第 8 章 结构体

结构体(struct) 是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。学过 C 或 C++ 的人都知道结构体,但在 Go 中,没有像 C++ 中的 class 类的概念,只有 struct 结构体的概念,所以也没有继承。

8.1 结构体的声明

在 Go 语言 中使用下面的语法是对结构体的声明。

1
2
3
4
5
type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}

例如下面是定义一个名为 Lesson(课程) 的结构体。

1
2
3
4
5
type Lesson struct {
name string //名称
target string //学习目标
spend int //学习花费时间
}

上面的代码声明了一个结构体类型 Lesson ,它有 nametargetspend 三个属性。可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑,如下面的代码所示。

1
2
3
4
type Lesson2 struct {
name, target string
spend int
}

上面的结构体 Lesson 称为 命名的结构体(Named Structure) 。我们创建了名为 Lesson 的新类型,而它可以用于创建 Lesson 类型的结构体变量。

声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure)

1
2
3
4
var Lesson3 struct {
name, target string
spend int
}

上面的代码创建了一个匿名结构体 lesson

8.2 创建命名的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

type Lesson struct {
name, target string
spend int
}

func main() {
// 使用字段名创建结构体
lesson1 := Lesson{
name: "《Go语言极简一本通》",
target: "学习Go语言,并完成一个单体服务",
spend: 5,
}
// 不使用字段名创建结构体
lesson2 := Lesson{"《Go语言极简一本通》", "学习Go语言,并完成一个单体服务", 5}

fmt.Println("lesson1 ", lesson1)
fmt.Println("lesson2 ", lesson2)
}

上面的例子使用了两种方法创建了结构体,第一种是在创建结构体时使用字段名对每个字段进行初始化,而第二种方法是在创建结构体时不使用字段名,直接按字段声明的顺序对字段进行初始化。

8.3 创建匿名结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
// 创建匿名结构体变量
lesson3 := struct {
name, target string
spend int
}{
name: "Go语言微服务架构核心22讲",
target: "掌握GO语言微服务方法论,全方位了解每个组件的作用",
spend: 3,
}

fmt.Println("lesson3 ", lesson3)
}

8.4 结构体的零值(Zero Value)

当定义好的结构体没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

type Lesson struct {
name, target string
spend int
}

func main() {
// 不初始化结构体
var lesson4 = Lesson{}

fmt.Println("lesson4 ", lesson4)
}

8.5 初始化结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type Lesson struct {
name, target string
spend int
}

func main() {
// 为结构体指定字段赋初值
var lesson5 = Lesson{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
}

// 上面的结构体变量 lesson5 只初始化了 name 和 target 字段, spend字段没有初始化,所以会被初始化为零值
fmt.Println("lesson5 ", lesson5)
}

8.6 访问结构体的字段

点操作符 . 用于访问结构体的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type Person struct {
name, gender string
age int
}

func main() {
var lesson6 = Lesson{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
spend: 50,
}

fmt.Println("lesson6 name: ", lesson6.name)
fmt.Println("lesson6 target: ", lesson6.target)
fmt.Println("lesson6 spend: ", lesson6.spend)
}

当然,使用点操作符 . 可以用于对结构体的字段的赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

type Lesson struct {
name, target string
spend int
}

func main() {
var lesson7 = Lesson{}
lesson7.name = "从0到Go语言微服务架构师"
lesson7.target = "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。"
lesson7.spend = 50
fmt.Println("lesson7 ", lesson7)
}

8.7 指向结构体的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

type Lesson struct {
name, target string
spend int
}

func main() {
lesson8 := &Lesson{"从0到Go语言微服务架构师", "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。", 50}
fmt.Println("lesson8 name: ", (*lesson8).name)
fmt.Println("lesson8 name: ", lesson8.name)
}

在上面的程序中, lesson8 是一个指向结构体 Lesson 的指针,上面用 (*lesson8).name 访问 lesson8name 字段,上面的 lesson8.name 代替 (*lesson8).name 的解引用访问。

Tip:

  • 注意:学过 C 语言的同学会认为lesson8->name才是正确的访问形式,但是在 Go 语言中,没有->访问的形式,访问结构体中的字段统一都是用.操作符

8.8 匿名字段

在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

type Lesson4 struct {
string
int
}

func main() {
lesson9 := Lesson4{"从0到Go语言微服务架构师", 50}
fmt.Println("lesson9 ", lesson9)
fmt.Println("lesson9 string: ", lesson9.string)
fmt.Println("lesson9 int: ", lesson9.int)
}

上面的程序结构体定义了两个匿名字段,虽然这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以上面的结构体 Lesoon4 有两个名为 stringint 的字段。

8.9 嵌套结构体

结构体的字段也可能是另外一个结构体,这样的结构体称为 嵌套结构体(Nested Structs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Author struct {
name string
wx string
}

type Lesson5 struct {
name,target string
spend int
author Author
}

func main() {
lesson10 := Lesson5{
name: "从0到Go语言微服务架构师",
spend: 50,
}
lesson10.author = Author{
name: "欢喜哥",
wx: "write_code_666",
}
fmt.Println("lesson10 name:", lesson10.name)
fmt.Println("lesson10 spend:", lesson10.spend)
fmt.Println("lesson10 author name:", lesson10.author.name)
fmt.Println("lesson10 author wx:", lesson10.author.wx)
}

上面的程序 Lesson5 结构体有一个字段 author ,而且它的类型也是一个结构体 Author

8.10 提升字段

结构体中如果有匿名的结构体类型字段,则该匿名结构体里的字段就称为 提升字段(Promoted Fields) 。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。就像刚刚上面的程序,如果我们把 Lesson 结构体中的字段 author 直接用匿名字段 Author 代替, Author 结构体的字段例如 name 就不用像上面那样使用 lesson10.author.wx 访问,而是使用 lesson10.wx 就能访问 Author 结构体中的 wx 字段。现在结构体 Authornamewx 两个字段,访问字段就像在 Lesson 里直接声明的一样,因此我们称之为提升字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import "fmt"

type Author struct {
name string
wx string
}

type Lesson6 struct {
name,target string
spend int
Author
}

func main() {
lesson10 := Lesson6{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
}
lesson10.author = Author{
name: "欢喜哥",
wx: "write_code_666",
}
fmt.Println("lesson10 name:", lesson10.name)
fmt.Println("lesson10 target:", lesson10.target)
fmt.Println("lesson10 author wx:", lesson10.wx)
}

8.11 结构体比较

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 ==!= 运算符进行比较。可以通过==运算符或 DeeplyEqual()函数比较两个结构相同的类型并包含相同的字段值。因此下面两个比较的表达式是等价的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Lesson struct{
name,target string
spend int
}

func main() {
lesson11 := Lesson{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
}
lesson12 := Lesson{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
}
fmt.Println(lesson11.name == lesson12.name && lesson11.target == lesson12.target) // true
fmt.Println(lesson11 == lesson12) // true
}

8.12 给结构体定义方法

在 Go 中无法在结构体内部定义方法,这一点与 C 语言类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
name, target string
spend int
}

// ShowLessonInfo 定义一个与 Person 的绑定的方法
func (l Lesson) ShowLessonInfo() {
fmt.Println("name:", l.name)
fmt.Println("target:", l.target)
}

func main() {
lesson13 := Lesson{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
}
lesson13.ShowPersonInfo()
}

上面的程序中定义了一个与结构体 Lesson 绑定的方法 ShowLessonInfo() ,其中 ShowLessonInfo 是方法名, (l Lesson) 表示将此方法与 Lesson 的实例绑定,这在 Go 语言中称为接收者,而 l 表示实例本身,相当于 Python 中的 self ,在方法内可以使用 实例本身.属性名称 来访问实例属性。

8.13 方法的参数传递方式

如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import "fmt"

// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
name,target string
spend int
}

// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
func (l Lesson) ShowLessonInfo() {
fmt.Println("name:", l.name)
fmt.Println("target:", l.target)
}

// AddTime 定义一个与 Lesson 的绑定的方法,使 spend 值加 n
func (l *Lesson) AddTime(n int) {
l.spend = l.spend + n
}

func main() {
lesson13 := Lesson{
name: "从0到Go语言微服务架构师",
target: "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。",
spend:50,
}
fmt.Println("添加add方法前")
lesson13.ShowLessonInfo()
lesson13.AddTime(5)
fmt.Println("添加add方法后")
lesson13.ShowLessonInfo()
}

如何学习Go语言微服务,快速步入架构师

从0到Go语言微服务架构师-海报 从0到Go语言微服务架构师
添加微信 公众号更多内容
wechat gzh