網(wǎng)站備案截圖網(wǎng)站收錄免費(fèi)咨詢
目錄
- 4. struct 結(jié)構(gòu)體
- 4.1 初始化
- 4.2 內(nèi)嵌字段
- 4.3 可見性
- 4.4 方法與函數(shù)
- 4.4.1 區(qū)別
- 4.4.2 閉包
- 4.5 Tag 字段標(biāo)簽
- 4.5.1定義
- 4.5.2 Tag規(guī)范
- 4.5.3 Tag意義
4. struct 結(jié)構(gòu)體
go的結(jié)構(gòu)體類似于其他語(yǔ)言中的class,主要區(qū)別就是go的結(jié)構(gòu)體沒有繼承這一概念,但可以使用類型嵌入來(lái)實(shí)現(xiàn)相似功能。
4.1 初始化
使用type關(guān)鍵字來(lái)定義一個(gè)新的類型,struct將新類型限定為結(jié)構(gòu)體類型。
結(jié)構(gòu)體中的字段可以為任何類型,但是包含一些特殊類型 如:接口,管道,函數(shù),指針的時(shí)候要格外注意
//type定義一個(gè)新類型
type newInt int//type定義一個(gè)簡(jiǎn)單的結(jié)構(gòu)體
type base struct{value int
}//type定義一個(gè)復(fù)雜的結(jié)構(gòu)體
type student struct {Name stringage intc interface{}d func() inte func()base //將base類型嵌入到了student類型中
}
4.2 內(nèi)嵌字段
內(nèi)嵌字段大體上有兩種方式:顯式指定(m1)和隱式指定(m2)
- 顯式指定就相當(dāng)于把目標(biāo)結(jié)構(gòu)體當(dāng)作字段,調(diào)用時(shí)需要先調(diào)用這個(gè)字段,在調(diào)用目標(biāo)結(jié)構(gòu)體中的信息
- 隱式指定相當(dāng)于把目標(biāo)結(jié)構(gòu)體中的所有字段都在新結(jié)構(gòu)體中創(chuàng)建了一次,并且指向嵌入結(jié)構(gòu)體內(nèi)部。同時(shí)創(chuàng)建同名嵌入結(jié)構(gòu)體對(duì)象[指與base同名]
- 顯式創(chuàng)建同名結(jié)構(gòu)體字段 ≠ 隱式指定
type base struct {Value int
}
//顯式指定
type m1 struct {b base
}//隱式指定
type m2 struct {base
}//顯式指定同名字段
type m3 struct {base base
}
對(duì)上述結(jié)構(gòu)體進(jìn)行調(diào)用:
- 只有隱式指定直接操作被嵌入結(jié)構(gòu)體內(nèi)的數(shù)據(jù);
- 隱式指定后,直接操作嵌入結(jié)構(gòu)體中的數(shù)據(jù)和通同名結(jié)構(gòu)體操作作用一樣
func main() {a1 := m1{}a2 := m2{}a3 := m3{}//顯式指定只能通過(guò)嵌入結(jié)構(gòu)體進(jìn)行操作// a1.Value = 1 //a1.Value undefined (type m2 has no field or method Value)a1.b.Value = 2//隱式指定兩種操作數(shù)據(jù)方法操作的是同一個(gè)變量a2.Value = 2a2.base.Value = 3fmt.Println(a2.Value) //3//顯式指定同名變量 ≠ 隱式指定 // a3.Value = 3 //a3.Value undefined (type m3 has no field or method Value)a3.base.Value = 4
}
當(dāng)內(nèi)嵌字段中的字段與結(jié)構(gòu)體中得字段同名時(shí):
- 直接調(diào)用時(shí)是指定當(dāng)前結(jié)構(gòu)體中顯式定義的字段,但嵌入結(jié)構(gòu)體中的字段仍可通過(guò)嵌入類型進(jìn)行調(diào)用
- 方法同理
//數(shù)據(jù)
func main() {a1 := m1{}a1.Value = "hello world"a1.base.Value = 1fmt.Println(a1)//獲取a1中的所有字段類型t := reflect.TypeOf(a1)for i := 0; i < t.NumField(); i++ {field := t.Field(i)fieldType := field.Typefmt.Printf("Field: %s, Type: %s\n", field.Name, fieldType.Name())}
}type base struct {Value int
}type m1 struct {Value stringbase
}
/*
{hello world {1}}
Field: Value, Type: string
Field: base, Type: base*/
//方法
func main() {a1 := m1{}a1.test()a1.base.test()fmt.Println(a1)//獲取a1中的所有字段類型t := reflect.TypeOf(a1)for i := 0; i < t.NumField(); i++ {field := t.Field(i)fieldType := field.Typefmt.Printf("Field: %s, Type: %s\n", field.Name, fieldType.Name())}
}type base struct {Value int
}func (b *base) test() {b.Value = 1
}
func (m *m1) test() {m.Value = "hello world"
}type m1 struct {Value stringbase
}
/*
{hello world {1}}
Field: Value, Type: string
Field: base, Type: base*/
4.3 可見性
- 首字母大寫表示該字段/方法/結(jié)構(gòu)體為可導(dǎo)出的,反之為不可導(dǎo)出的
- 在同一個(gè)包內(nèi),不區(qū)分是否可導(dǎo)出,都可訪問(wèn);包外只能訪問(wèn)可導(dǎo)出的字段/方法/結(jié)構(gòu)體
- 不可導(dǎo)出的字段不可以進(jìn)行序列化(轉(zhuǎn)化為json)
- 可通過(guò)可導(dǎo)出的方法去操作不可導(dǎo)出的字段
// test/test1.go
package testtype User struct {Name stringage int
}func (u *User) Test() {u.Name = "hello"u.age = 18
}func (u *User) test() {u.Name = "world"u.age = 81
}type student struct {Name stringage int
}
//main.go
package mainimport ("fmt""test/test"
)func main() {a := test.User{}//b := test.student{} // 不能在包外訪問(wèn)未導(dǎo)出結(jié)構(gòu)體a.Name = "123"//a.age = 123 // 不能在包外訪問(wèn)未導(dǎo)出字段a.Test()//a.test() // 不能在包外訪問(wèn)未導(dǎo)出方法fmt.Println(a)/*{hello 18}*/
}
4.4 方法與函數(shù)
4.4.1 區(qū)別
- 方法定義是必須有一個(gè)接收器(receiver);函數(shù)不需要
- 大部分情況下,方法的調(diào)用需要有一個(gè)對(duì)象;函數(shù)不需要
- 由于go中的所有傳遞都是值傳遞,也就是將數(shù)據(jù)復(fù)制一份再調(diào)用,所以如果想要修改原本對(duì)象的值,就要傳遞指針,進(jìn)行引用傳遞
func 函數(shù)名 (參數(shù)) 返回值類型 {函數(shù)體} //函數(shù)定義
func (接收器) 方法名 (參數(shù)) 返回值類型 {函數(shù)體} // 方法定義
func main() {a := User{}a.setName() // 方法調(diào)用fmt.Println(a)setName(&a) // 函數(shù)調(diào)用fmt.Println(a)
}
/*{world 0}{hello 0}*/type User struct {Name stringage int
}
//函數(shù)
func setName(u *User) {u.Name = "hello"
}
//方法
func (u *User) setName() {u.Name = "world"
}
值傳遞時(shí)可以通過(guò)返回值的方式修改目標(biāo)對(duì)象
func main() {a := User{}a = a.setName() //需要顯式給原變量賦值fmt.Println(a)a = setName(a) //需要顯式給原變量賦值fmt.Println(a)
}type User struct {Name stringage int
}func setName(u User) User {u.Name = "hello"return u
}
func (u User) setName() User {u.Name = "world"return u
}
4.4.2 閉包
說(shuō)到函數(shù)和方法,就必須說(shuō)一下閉包
什么是閉包?
簡(jiǎn)單來(lái)說(shuō),就是函數(shù)內(nèi)部引用函數(shù)外部變量,導(dǎo)致變量生命周期發(fā)生變化。這樣的函數(shù)就叫做閉包
常見于函數(shù)返回值為另一個(gè)函數(shù)時(shí)
package mainimport "fmt"func main() {b := test()fmt.Println(b())fmt.Println(b())
}func test() func() int {a := 1return func() int {a++return a}
}
上面的函數(shù)導(dǎo)致變量a無(wú)法正常釋放,導(dǎo)致變量逃逸
go build -gcflags="-m" main.go
# command-line-arguments
./main.go:11:6: can inline test
./main.go:13:9: can inline test.func1
./main.go:6:11: inlining call to test
./main.go:13:9: can inline main.test.func1
./main.go:7:15: inlining call to main.test.func1
./main.go:7:13: inlining call to fmt.Println
./main.go:8:15: inlining call to main.test.func1
./main.go:8:13: inlining call to fmt.Println
./main.go:6:11: func literal does not escape
./main.go:7:13: ... argument does not escape
./main.go:7:15: ~R0 escapes to heap
./main.go:8:13: ... argument does not escape
./main.go:8:15: ~R0 escapes to heap
./main.go:12:2: moved to heap: a
./main.go:13:9: func literal escapes to heap
4.5 Tag 字段標(biāo)簽
4.5.1定義
在reflect包中提供了獲取字段名稱、類型、Tag的方法(上文展示過(guò)獲取名稱和類型)
結(jié)構(gòu)體StructField表示結(jié)構(gòu)體的一個(gè)字段(reflect/type.go)
// A StructField describes a single field in a struct.
type StructField struct {// Name is the field name.Name string// PkgPath is the package path that qualifies a lower case (unexported)// field name. It is empty for upper case (exported) field names.// See https://golang.org/ref/spec#Uniqueness_of_identifiersPkgPath stringType Type // field typeTag StructTag // field tag stringOffset uintptr // offset within struct, in bytesIndex []int // index sequence for Type.FieldByIndexAnonymous bool // is an embedded field
}type StructTag string
4.5.2 Tag規(guī)范
StructTag本質(zhì)上就是字符串,理論上任何形式都符合規(guī)范。但通常情況下約定,Tag的格式應(yīng)該是key:“value”
- key:非空字符串,不能包含控制字符,空格,引號(hào),冒號(hào)
- value:雙引號(hào)包圍的字符串
- 冒號(hào)前后不能有空格,多個(gè)value用逗號(hào)隔開,key之間用空格隔開
- key一般表示用途,value表示控制指令;
4.5.3 Tag意義
- Go語(yǔ)言反射機(jī)制可以給結(jié)構(gòu)體成員賦值,用Tag可以決定賦值的動(dòng)作
- 可以使用定義好的Tag規(guī)則,參考規(guī)則就可以繼續(xù)不同的操作
//僅對(duì)Tag值為true的字段賦值(Tag決定賦值動(dòng)作)
type Person struct {Name string `assign:"true"`Age int `assign:"false"`
}func assignValues(v interface{}) {val := reflect.ValueOf(v).Elem() // 獲取指針指向的值typ := val.Type()for i := 0; i < val.NumField(); i++ {field := val.Field(i)tag := typ.Field(i).Tag.Get("assign") // 獲取字段的tagif tag == "true" {// 根據(jù)字段類型賦值switch field.Kind() {case reflect.String:field.SetString("Default Name")case reflect.Int:field.SetInt(25)}}}
}func main() {p := &Person{}assignValues(p)fmt.Printf("Person: %+v\n", p)
}
/*
Person: &{Name:Default Name Age:0}*/
下方例子使用了json:“kind,omitempty”,這個(gè)tag規(guī)定了字段為空不進(jìn)行序列化;
import ("encoding/json""fmt"
)type Person struct {Name string `json:"kind,omitempty"` //為空時(shí)不進(jìn)行序列化Value intage int
}func main() {// 創(chuàng)建一個(gè) Person 實(shí)例p := Person{Name: "", Value: 100, age: 100}// 序列化為 JSONjsonData, _ := json.Marshal(p)fmt.Println(string(jsonData)) /*{"Value":100}*/
}