结构体(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)
}