第 8 章 结构体
结构体(struct) 是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。学过 C 或 C 的人都知道结构体,但在 Go 中,没有像 C 中的 class
类的概念,只有 struct
结构体的概念,所以也没有继承。
8.1 结构体的声明
在 Go 语言 中使用下面的语法是对结构体的声明。
代码语言:go复制type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}
例如下面是定义一个名为 Lesson(课程)
的结构体。
type Lesson struct {
name string //名称
target string //学习目标
spend int //学习花费时间
}
上面的代码声明了一个结构体类型 Lesson
,它有 name
、 target
和 spend
三个属性。可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑,如下面的代码所示。
type Lesson2 struct {
name, target string
spend int
}
上面的结构体 Lesson
称为 命名的结构体(Named Structure) 。我们创建了名为 Lesson
的新类型,而它可以用于创建 Lesson
类型的结构体变量。
声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure) 。
代码语言:go复制var Lesson3 struct {
name, target string
spend int
}
上面的代码创建了一个匿名结构体 lesson
。
8.2 创建命名的结构体
代码语言:go复制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 创建匿名结构体
代码语言:go复制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)
当定义好的结构体没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。
代码语言:go复制package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 不初始化结构体
var lesson4 = Lesson{}
fmt.Println("lesson4 ", lesson4)
}
8.5 初始化结构体
代码语言:go复制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 访问结构体的字段
点操作符 .
用于访问结构体的字段。
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)
}
当然,使用点操作符 .
可以用于对结构体的字段的赋值。
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 指向结构体的指针
代码语言:go复制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
访问 lesson8
的 name
字段,上面的 lesson8.name
代替 (*lesson8).name
的解引用访问。
Tip:
- 注意:学过 C 语言的同学会认为
lesson8->name
才是正确的访问形式,但是在 Go 语言中,没有->访问的形式,访问结构体中的字段统一都是用.
操作符
8.8 匿名字段
在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field) 。
代码语言:go复制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
有两个名为 string
和 int
的字段。
8.9 嵌套结构体
结构体的字段也可能是另外一个结构体,这样的结构体称为 嵌套结构体(Nested Structs)
代码语言:go复制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
字段。现在结构体 Author
有 name
、 wx
两个字段,访问字段就像在 Lesson
里直接声明的一样,因此我们称之为提升字段。
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()函数比较两个结构相同的类型并包含相同的字段值。因此下面两个比较的表达式是等价的:
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 语言类似。
代码语言:go复制package main
import "fmt"
// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
name,target string
spend int
}
// PrintPersonInfo 定义一个与 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 方法的参数传递方式
如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者。
代码语言:go复制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()
}