结构体(struct)是用户自定义的类型,它代表若干字段的集合。有些时候将多个数据看做一个整体要比单独使用这些数据更有意义,这种情况下就适合使用结构体。比如将一个员工的 firstName, lastName 和 age 三个属性打包在一起成为一个 employee 结构就是很有意义的。其使用需要遵循以下要求:

  • 使用type struct{} 定义结构,名称遵循可见性规则
  • 支持指向自身的指针类型成员
  • 支持匿名结构,可用作成员或定义成员变量
  • 匿名结构也可以用于map的值
  • 可以使用字面值对结构进行初始化
  • 允许直接通过指针来读写结构成员
  • 相同类型的成员可进行直接拷贝赋值
  • 支持与!=比较运算符,但不支持>或

一、结构体的定义

一般情况下对结构体进行定义时,结构体里的字段要求大写,因为小写为私有定义,这就导致在写模块时,该模块对应的结构体能容不能导出,所以需要大写。其使用方法如下:

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct { //结构也是一中类型
 6    Name string //定义struct的属性
 7    Age  int
 8}
 9func main() {
10    a := Person{}
11    a.Name = "joe" //对struct的属性进行操作,类型与class的使用方法
12    a.Age = 19
13    fmt.Println(a)
14}

对结构体赋值时,也可以直接进行赋值,如下:

1a := Person{
2    Name: "jack",
3    Age:  19, //对结构的属性进行字面值的初始化
4}

二、直接赋值与指针赋值

结构体在进行赋值时,有直接赋值和指针赋值的区别。指针赋值是针对的结构体的内存地址。修改值内容后,源数据也会变化,而直接赋值是在修改后,原数据只在调用只时会变化,调用完成后还会变成原来的值,因为是值拷贝。

1、直接赋值(值拷贝)

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct {
 6    Name string
 7    Age  int
 8}
 9func main() {
10    a := Person{
11        Name: "jack",
12        Age:  19, //对结构的属性进行字面值的初始化
13    }
14    fmt.Println(a)
15    A(a)
16    fmt.Println(a) //结构也是一种值类型,对它进行传递的时候,也是进行了值得拷贝
17}
18func A(per Person) {
19    per.Age = 13
20    fmt.Println("A", per)
21}
22PS G:\mygo\src\mytest> go run .\temp.go
23{jack 19}
24A {jack 13}
25{jack 19}

2、指针赋值

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct {
 6    Name string
 7    Age  int
 8}
 9func main() {
10    a := Person{
11        Name: "jack",
12        Age:  19, //对结构的属性进行字面值的初始化
13    }
14    fmt.Println(a)
15    A(&a)
16    fmt.Println(a) //结构也是一种值类型,对它进行传递的时候,也是进行了值得拷贝
17}
18func A(per *Person) { //通过一个指针进行传递,此时就不是值得拷贝了
19    per.Age = 13
20    fmt.Println("A", per)
21}
22PS G:\mygo\src\mytest> go run .\temp.go
23{jack 19}
24A &{jack 13}
25{jack 13}

3、初始化指针

在进行结构体初始化赋值时,可以直接就取结构体的指针地址。这样会使读取的速度更快,如下:

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct {
 6    Name string
 7    Age  int
 8}
 9func main() {
10    a := &Person{
11        Name: "jack",
12        Age:  19, //此时初始化的时候就将这个struct的指针取出来
13    }
14    //在进行struct的初始化的时候,就加上&取地址符号
15    fmt.Println(a)
16    A(a)
17    B(a)
18    fmt.Println(a) //结构也是一种值类型,对它进行传递的时候,也是进行了值得拷贝
19}
20func A(per *Person) { //通过一个指针进行传递,此时就不是值得拷贝了
21    per.Age = 13
22    fmt.Println("A", per)
23}
24func B(per *Person) { //通过一个指针进行传递,此时就不是值得拷贝了
25    per.Age = 15
26    fmt.Println("B", per)
27}
28PS G:\mygo\src\mytest> go run .\temp.go
29&{jack 19}
30A &{jack 13}
31B &{jack 15}
32&{jack 15}

二、匿名结构

匿名结构分类为:一种是不给结构体进行命名,直接进行赋值并使用;另一种是字段匿名,只指明类型,然后直接使用。

1、匿名结构

 1package main
 2import (
 3    "fmt"
 4)
 5func main() {
 6    a := &struct { //匿名结构,需要先对结构本身进行一个定义
 7        Name string
 8        Age  int
 9    }{
10        Name: "jack",
11        Age:  20,
12    }
13    fmt.Println(a)
14}

2、匿名结构的嵌套

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct {
 6    Name    string
 7    Age     int
 8    Contact struct {
 9        Phone, City string //匿名结构嵌套在Person中
10    }
11}
12func main() {
13    a := Person{Name: "Jack", Age: 20}
14    a.Contact.Phone = "123321" //通过这种方法对嵌套在Person中的匿名结构进行字面值的初始化
15    a.Contact.City = "BeiJing"
16    fmt.Println(a)
17}
18PS G:\mygo\src\mytest> go run .\temp2.go
19{Jack 20 {123321 BeiJing}}

3、匿名字段

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct {
 6    string //匿名字段 在进行字面值初始化的时候 必须严格按照字段声明的顺序
 7    int
 8}
 9func main() {
10    a := Person{"Jack", 20} //此时将string 和 int类型对调的时候就会报错
11    fmt.Println(a)
12}

三、结构体的嵌套与比较

1、结构体的嵌套

 1package main
 2import (
 3    "fmt"
 4)
 5type human struct {
 6    Sex int
 7}
 8type teacher struct {
 9    human
10    Name string
11    Age  int
12}
13type student struct {
14    human //这里的human也是一种类型,此时它相当于一种匿名字段,嵌入结构作为匿名字段的话
15    //它本质上是将结构名称作为我们的字段名称
16    Name string
17    Age  int
18}
19func main() {
20    a := teacher{Name: "Jack", Age: 20, human: human{Sex: 0}} //因此我们需要在这里进行这种初始化
21    b := student{Name: "Tom", Age: 19, human: human{Sex: 1}}
22    a.Name = "Fack"
23    a.Age = 13
24    a.human.Sex = 100 //保留这种调用的方法,是因为会涉及到名称的冲突
25    //a.Sex = 101 这种写法也是可以的
26    fmt.Println(a, b)
27}
28PS G:\mygo\src\mytest> go run .\temp3.go
29{{100} Fack 13} {{1} Tom 19}

2、结构体的比较

 1package main
 2import (
 3    "fmt"
 4)
 5type Person struct {
 6    Name string
 7    Age  int
 8}
 9func main() {
10    a := Person{Name: "Jack", Age: 20}
11    b := Person{Name: "Jack", Age: 20}
12    fmt.Println(a == b)
13}
14PS G:\mygo\src\mytest> go run .\temp3.go
15true

四、tag标签的使用

这个比较多的使用场景是json和结构体的转换中,因为结构体中字段的首字母要大写,而json中的数据不见得都是首字母都是大写的,这时候就可以通过tag字段实现该功能,如下:

 1package main
 2import (
 3    "encoding/json"
 4    "fmt"
 5)
 6type peerInfo struct {
 7    HTTPPort int `json:"http_port"`
 8    TCPPort  int `json:"tcp_port"`
 9    versiong string
10}
11func main() {
12    var v peerInfo
13    data := []byte(`{"http_port":80,"tcp_port":3306}`)
14    err := json.Unmarshal(data, &v)
15    if err != nil {
16        fmt.Println(err)
17    }
18    fmt.Printf("%+v\n", v)
19}

后面小写的tag标签就与json中的数据保持了一致。

json转为struct结构体可以在如下两个网站上进行转换,https://mholt.github.io/json-to-go/ ,https://oktools.net/json2go 。

通过反射,获取tag,进行打印的代码如下:

 1package main
 2import (
 3    "fmt"
 4    "reflect"
 5    "strings"
 6)
 7type Person struct {
 8    Name        string `label:"Person Name: " uppercase:"true"`
 9    Age         int    `label:"Age is: "`
10    Sex         string `label:"Sex is: "`
11    Description string
12}
13// 按照tag打印结构体
14func PrintUseTag(ptr interface{}) error {
15    // 获取入参的类型
16    t := reflect.TypeOf(ptr)
17    // 入参类型校验
18    if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
19        return fmt.Errorf("参数应该为结构体指针")
20    }
21    // 取指针指向的结构体变量
22    v := reflect.ValueOf(ptr).Elem()
23    // 解析字段
24    for i := 0; i < v.NumField(); i++ {
25        // 取tag
26        fieldInfo := v.Type().Field(i)
27        tag := fieldInfo.Tag
28        // 解析label tag
29        label := tag.Get("label")
30        if label == "" {
31            label = fieldInfo.Name + ": "
32        }
33        // 解析uppercase tag
34        value := fmt.Sprintf("%v", v.Field(i))
35        if fieldInfo.Type.Kind() == reflect.String {
36            uppercase := tag.Get("uppercase")
37            if uppercase == "true" {
38                value = strings.ToUpper(value)
39            } else {
40                value = strings.ToLower(value)
41            }
42        }
43        fmt.Println(label + value)
44    }
45    return nil
46}
47func main() {
48    person := Person{
49        Name:        "Tom",
50        Age:         29,
51        Sex:         "Male",
52        Description: "Cool",
53    }
54    PrintUseTag(&person)
55}