Go 学习笔记4 - 结构体 |Go主题月

174 阅读3分钟

结构体是将零个或多个任意类型的命名变量组合在一起的聚合数据类型。每个变量叫做结构体的成员

成员变量名称是字母大写,则变量可导出(包外可修改、json 序列化后可见)

  • 使用type Name struct{}定义结构体
  • 支持指向自身的指针类型成员
  • 支持匿名结构,可用作成员或定义成员变量
  • 匿名结构也可以用于map的值
  • 可以使用字面值对结构体进行初始化
  • 允许直接通过指针来读写结构成员
  • 相同类型的成员可以进行直接拷贝赋值
  • 支持==与!=比较运算符,但不支持>或<
  • 支持匿名字段,本质上定义了以某个类型名为名称的字段
  • 嵌入结构作为匿名字段看起来像继承,但不是继承
  • 可以使用匿名字段指针

1.结构体的定义

// 官方 time 包定义的纳秒类型
type Duration int64

// 用户自定义的雇员类型
type Employee struct {
	ID            int
	Name, Address string // 相同类型的可放置在一行
	salary        int
	ManagerID     int
}

// 二叉树结构
type TreeNode struct {
	Val         int
	Left, Right *TreeNode // 可引用自身或其他结构
}

初始化类型:

var person Employee

上面使用 var 声明了一个类型为 Employee 的变量 person。此时 person 结构体成员的值都是对应类型零值。

使用字面量初始化类型:

func main() {
  // 顺序必须和声明的类型成员一致,每个成员都必须给定值
  person1 := Employee{1, "张三", "四川成都", 5000, 1}

  // 顺序随便且可以不用每个成员都给定值
  person2 := Employee{
    Name:    "李四",
    Address: "四川绵阳",
    ID:      2,
    salary:  5000,
  }
  
  person3 := new(Employee) // new 方法初始化,返回的是一个指针
	person3.Name = "王五"
  
  // 结构体访问
  person1.Salary = 6000
  person2.Address = "四川成都"
  fmt.Println(person1, person2, person3)
}

输出:

{1 张三 四川成都 6000 1} {2 李四 四川成都 5000 0} &{0 王五  0 0}

2.匿名结构与字段

// 匿名结构
func main() {
	a := &struct {
		Name string
		Age  int
	}{
		Name: "marks",
		Age:  29,
	}
	fmt.Println(a)
}

// 匿名字段(初始化时,顺序很重要)
type person struct {
	string
	int
}

func main() {
	a:=&person{
		"张三",16,
	}
	fmt.Println(a)
}

3.结构间的赋值与比较

// 结构间的比较必须是同一种类型,并且值也要相等才是true
person2 := Employee{ManagerID: 1, Name: "王五"}
person3 := Employee{0, "王五", "", 0, 1}

fmt.Println(person2 == person3) // true

可比较的结构体类型都可以作为 map 的 key:

m := make(map[Employee]int)
m[Employee{Name: "Lucy"}] = 1

4.嵌入结构

package main

import "fmt"

type Human struct {
	Sex int
}

type student struct {
	Human
	Name string
	Age  int
}

type teacher struct {
	Human
	Name string
	Age  int
}

func main() {
	a := &student{
		Name:  "小明",
		Age:   12,
		Human: Human{Sex: 1}, // 嵌入结构,使用结构名作为嵌入的字段名
	}
	b := &teacher{
		Name:  "张老师",
		Age:   12,
		Human: Human{Sex: 2},
	}
	a.Sex = 2
	b.Human.Sex = 1   // 这种访问方法是为了避免名称的冲突
	fmt.Println(a, b)
}

5.Json

定义结构体很多时候是用来当做 http 接口的返回值,目前 http 接口基本都是用 json 来当做标准的返回值。

在 Go 里面通过标准库 encoding/json来实现 json 的序列化和反序列化操作:

序列化:

使用 json.Marshal()

type Resp struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}

type Employee struct {
	ID        int    `json:"-"` // 不输出字段
	Name      string `json:"name"`
	Address   string `json:"address"`
	salary    int    `json:"salary"`
	ManagerID int    `json:"manager_id,omitempty"` // 忽略掉零值的字段
}

func main() {
	resp := Resp{
		Code: 0,
		Msg:  "成功",
		Data: Employee{
			Name:    "李四",
			Address: "四川绵阳",
			ID:      2,
			salary:  5000,
		},
	}
	marshal, err := json.Marshal(&resp)
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Printf("%s\n", marshal)
}

输出:

{"code":0,"msg":"成功","data":{"name":"李四","address":"四川绵阳"}}

这里需要注意:

1.结构体成员变量后面的 tag 定义json序列化时字段的名称,如果未定义 json tag 则使用成员变量名称。

2.使用json:"-" 忽略掉字段。

3.json:"manager_id,omitempty",omitempty 忽略零值的字段。

反序列化

使用 json.Unmarshal()

str := `{"code":0,"msg":"成功","data":{"name":"李四","address":"四川绵阳"}}`
var p Resp
if err := json.Unmarshal([]byte(str), &p); err != nil {
  fmt.Println(err)
  return
}
fmt.Printf("%+v\n", p)

输出:

{Code:0 Msg:成功 Data:map[address:四川绵阳 name:李四]}