结构体(struct)是用户自定义的类型,它代表若干字段的集合。有些时候将多个数据看做一个整体要比单独使用这些数据更有意义,这种情况下就适合使用结构体。比如将一个员工的 firstName, lastName 和 age 三个属性打包在一起成为一个 employee 结构就是很有意义的。其使用需要遵循以下要求:
使用type struct{} 定义结构,名称遵循可见性规则 支持指向自身的指针类型成员 支持匿名结构,可用作成员或定义成员变量 匿名结构也可以用于map的值 可以使用字面值对结构进行初始化 允许直接通过指针来读写结构成员 相同类型的成员可进行直接拷贝赋值 支持==与!=比较运算符,但不支持>或< 支持匿名字段,本质上是定义了以某个类型名为名称的字段 嵌入结构作为匿名字段看起来像继承,但不是继承 可以使用匿名字段指针
一、结构体的定义
一般情况下对结构体进行定义时,结构体里的字段要求大写,因为小写为私有定义,这就导致在写模块时,该模块对应的结构体能容不能导出,所以需要大写。其使用方法如下:
package main import ( "fmt" ) type Person struct { //结构也是一中类型 Name string //定义struct的属性 Age int } func main() { a := Person{} a.Name = "joe" //对struct的属性进行操作,类型与class的使用方法 a.Age = 19 fmt.Println(a) }
对结构体赋值时,也可以直接进行赋值,如下:
a := Person{ Name: "jack", Age: 19, //对结构的属性进行字面值的初始化 }
二、直接赋值与指针赋值
结构体在进行赋值时,有直接赋值和指针赋值的区别。指针赋值是针对的结构体的内存地址。修改值内容后,源数据也会变化,而直接赋值是在修改后,原数据只在调用只时会变化,调用完成后还会变成原来的值,因为是值拷贝。
1、直接赋值(值拷贝)
package main import ( "fmt" ) type Person struct { Name string Age int } func main() { a := Person{ Name: "jack", Age: 19, //对结构的属性进行字面值的初始化 } fmt.Println(a) A(a) fmt.Println(a) //结构也是一种值类型,对它进行传递的时候,也是进行了值得拷贝 } func A(per Person) { per.Age = 13 fmt.Println("A", per) } PS G:\mygo\src\mytest> go run .\temp.go {jack 19} A {jack 13} {jack 19}
2、指针赋值
package main import ( "fmt" ) type Person struct { Name string Age int } func main() { a := Person{ Name: "jack", Age: 19, //对结构的属性进行字面值的初始化 } fmt.Println(a) A(&a) fmt.Println(a) //结构也是一种值类型,对它进行传递的时候,也是进行了值得拷贝 } func A(per *Person) { //通过一个指针进行传递,此时就不是值得拷贝了 per.Age = 13 fmt.Println("A", per) } PS G:\mygo\src\mytest> go run .\temp.go {jack 19} A &{jack 13} {jack 13}
3、初始化指针
在进行结构体初始化赋值时,可以直接就取结构体的指针地址。这样会使读取的速度更快,如下:
package main import ( "fmt" ) type Person struct { Name string Age int } func main() { a := &Person{ Name: "jack", Age: 19, //此时初始化的时候就将这个struct的指针取出来 } //在进行struct的初始化的时候,就加上&取地址符号 fmt.Println(a) A(a) B(a) fmt.Println(a) //结构也是一种值类型,对它进行传递的时候,也是进行了值得拷贝 } func A(per *Person) { //通过一个指针进行传递,此时就不是值得拷贝了 per.Age = 13 fmt.Println("A", per) } func B(per *Person) { //通过一个指针进行传递,此时就不是值得拷贝了 per.Age = 15 fmt.Println("B", per) } PS G:\mygo\src\mytest> go run .\temp.go &{jack 19} A &{jack 13} B &{jack 15} &{jack 15}
二、匿名结构
匿名结构分类为:一种是不给结构体进行命名,直接进行赋值并使用;另一种是字段匿名,只指明类型,然后直接使用。
1、匿名结构
package main import ( "fmt" ) func main() { a := &struct { //匿名结构,需要先对结构本身进行一个定义 Name string Age int }{ Name: "jack", Age: 20, } fmt.Println(a) }
2、匿名结构的嵌套
package main import ( "fmt" ) type Person struct { Name string Age int Contact struct { Phone, City string //匿名结构嵌套在Person中 } } func main() { a := Person{Name: "Jack", Age: 20} a.Contact.Phone = "123321" //通过这种方法对嵌套在Person中的匿名结构进行字面值的初始化 a.Contact.City = "BeiJing" fmt.Println(a) } PS G:\mygo\src\mytest> go run .\temp2.go {Jack 20 {123321 BeiJing}}
3、匿名字段
package main import ( "fmt" ) type Person struct { string //匿名字段 在进行字面值初始化的时候 必须严格按照字段声明的顺序 int } func main() { a := Person{"Jack", 20} //此时将string 和 int类型对调的时候就会报错 fmt.Println(a) }
三、结构体的嵌套与比较
1、结构体的嵌套
package main import ( "fmt" ) type human struct { Sex int } type teacher struct { human Name string Age int } type student struct { human //这里的human也是一种类型,此时它相当于一种匿名字段,嵌入结构作为匿名字段的话 //它本质上是将结构名称作为我们的字段名称 Name string Age int } func main() { a := teacher{Name: "Jack", Age: 20, human: human{Sex: 0}} //因此我们需要在这里进行这种初始化 b := student{Name: "Tom", Age: 19, human: human{Sex: 1}} a.Name = "Fack" a.Age = 13 a.human.Sex = 100 //保留这种调用的方法,是因为会涉及到名称的冲突 //a.Sex = 101 这种写法也是可以的 fmt.Println(a, b) } PS G:\mygo\src\mytest> go run .\temp3.go {{100} Fack 13} {{1} Tom 19}
2、结构体的比较
package main import ( "fmt" ) type Person struct { Name string Age int } func main() { a := Person{Name: "Jack", Age: 20} b := Person{Name: "Jack", Age: 20} fmt.Println(a == b) } PS G:\mygo\src\mytest> go run .\temp3.go true
四、tag标签的使用
这个比较多的使用场景是json和结构体的转换中,因为结构体中字段的首字母要大写,而json中的数据不见得都是首字母都是大写的,这时候就可以通过tag字段实现该功能,如下:
package main import ( "encoding/json" "fmt" ) type peerInfo struct { HTTPPort int `json:"http_port"` TCPPort int `json:"tcp_port"` versiong string } func main() { var v peerInfo data := []byte(`{"http_port":80,"tcp_port":3306}`) err := json.Unmarshal(data, &v) if err != nil { fmt.Println(err) } fmt.Printf("%+v\n", v) }
后面小写的tag标签就与json中的数据保持了一致。
json转为struct结构体可以在如下两个网站上进行转换,https://mholt.github.io/json-to-go/ ,https://oktools.net/json2go 。
通过反射,获取tag,进行打印的代码如下:
package main import ( "fmt" "reflect" "strings" ) type Person struct { Name string `label:"Person Name: " uppercase:"true"` Age int `label:"Age is: "` Sex string `label:"Sex is: "` Description string } // 按照tag打印结构体 func PrintUseTag(ptr interface{}) error { // 获取入参的类型 t := reflect.TypeOf(ptr) // 入参类型校验 if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { return fmt.Errorf("参数应该为结构体指针") } // 取指针指向的结构体变量 v := reflect.ValueOf(ptr).Elem() // 解析字段 for i := 0; i < v.NumField(); i++ { // 取tag fieldInfo := v.Type().Field(i) tag := fieldInfo.Tag // 解析label tag label := tag.Get("label") if label == "" { label = fieldInfo.Name + ": " } // 解析uppercase tag value := fmt.Sprintf("%v", v.Field(i)) if fieldInfo.Type.Kind() == reflect.String { uppercase := tag.Get("uppercase") if uppercase == "true" { value = strings.ToUpper(value) } else { value = strings.ToLower(value) } } fmt.Println(label + value) } return nil } func main() { person := Person{ Name: "Tom", Age: 29, Sex: "Male", Description: "Cool", } PrintUseTag(&person) }