《Go程序设计语言》学习笔记之结构体
《Go程序设计语言》学习笔记之结构体
一. 环境
Centos8.5, go1.17.5 linux/amd64
二. 概念
结构体是将零个或者多个任意类型的命名变量组合在一起的聚合数据类型。每个变量都叫做结构体的成员。
三. 声明/初始化
1) 定义一个结构体类型 Human,然后通过它定义一个命名结构体变量 child。
8 type Human struct { 9 Name string 10 Age int 11 Height float64 12 } 13 14 var child Human 15 16 func main() { 17 child.Name = "Jim" 18 child.Height = 90.0 19 child.Age = 4 20 21 fmt.Printf("%#v\n", child) 22 }
运行结果如下
结构体的成员变量通常一行写一个,变量的名称在类型的前面,但是相同类型的连续成员变量可以写在同一行上。
成员变量的顺序对于结构体同一性很重要,如果我们调整了成员的顺序,那么我们就在定义一个不同的结构体类型。
如果一个结构体的成员变量名称是首字母大写的,那么这个变量是可导出的,这个是Go最主要的访问控制机制。一个结构体可以同时包含可导出和不可导出的成员变量。
命名结构体类型 S 中,不可以定义一个拥有相同结构体类型 S 的成员变量,也就是一个聚合类型不可以包含它自己。否则,编译会报错,提示“无效的递归类型”,如下
但是,结构体类型 S 中 可以定义一个 S 的指针类型,即 *S ,这样可以创建一些递归数据结构如链表、树。
2) 零值
结构体的零值由结构体成员的零值组成。通常情况下,零值是一个默认的自然的、合理的值。
26 type Test struct { 27 Size int 28 Buffer string 29 IsBool bool 30 Mut sync.Mutex 31 } 32 33 func main() { 34 t1 := Test{} 35 fmt.Printf("%#v\n", t1) 36 }
运行结果如下
结构体类型 Test 前面三个成员的零值,整型的零值为 0,字符串类型的成员零值为空字符串, 布尔类型的成员零值为 false,sync.Mutex类型的成员是一个可以直接使用且未锁定状态的互斥锁。
3) 通过结构体字面量来初始化结构体变量
一种方式是按照正确的顺序,为每个成员变量指定一个值,如 p1。这种方式适用于小型的大家都比较容易理解的结构体,比如 Point 类型结构体。
另一种方式是通过指定部分或全部成员变量的名称和值来初始化结构体变量,如 p2。这种方式一般用的较多。
8 type Point struct { 9 X float64 10 Y float64 11 } 12 13 func main() { 14 p1 := Point{3.0, 4.0} 15 p2 := Point{X: 2.0, Y: 5.0} 16 17 fmt.Printf("%v\n", p1) 18 fmt.Printf("%v\n", p2) 19 }
初始化一个结构体变量的指针
20 pp := &Point{5, 8} 21 fmt.Printf("%v\n", *pp)
这个等价于
23 pp2 := new(Point) 24 *pp2 = Point{5, 8} 25 fmt.Printf("%v\n", *pp2)
&Point{5, 8} 这种方式可以直接使用在一个表达式中,例如函数调用。
四. 访问
结构体的每个成员都可以通过点号方式来访问,也可以获取成员变量的地址,然后通过指针来访问它。
h1 通过点号来访问成员 Age,h1 通过指针修改了成员变量 Height 的值。
19 func main() { 20 h1 := Human{"Jim", 21, 65} 21 22 h1.Age++ 23 *(&h1.Height) += 2.0 24 fmt.Printf("%#v\n", h1) 25 }
运行结果如下
五. 比较
如果结构体的所有成员变量都可以比较,那么这个结构体就是可比较的。两个结构体的比较可以使用 == 或者 !=。== 操作符按照顺序比较两个结构体变量的成员变量。
第28和第29两行代码的语句是等价的
26 p1 := Point{1, 2} 27 p2 := Point{2, 1} 28 fmt.Printf("%t\n", p1.X == p2.X && p1.Y == p2.Y) 29 fmt.Printf("%t\n", p1 == p2)
可比较的类型都可以做为 map 的键类型,结构体变量 addr 作为 map 变量 acce 的键值
19 type address struct { 20 Ip string 21 Port string 22 } 23 24 func main() { 25 addr := address{"127.0.0.1", "9090"} 26 acce := make(map[address]int) 27 acce[addr]++ 28 fmt.Printf("%v\n", acce) 29 }
运行结果如下
六. 使用
1) 作函数参数
一般使用指针传递的方式,一是出于效率的考虑,二 是当需要结构体变量时这种方式就是必需的。
值传递,调用者接收到的实参的一个副本,并不是实参的引用。
2) 结构体嵌套和匿名成员
a. 结构体嵌套,这个机制可以让我们将一个命名结构体当作另一个结构体类型的匿名成员使用,并提供了方便的访问方式。
借用书上的例子,下面示例代码,结构体中嵌套了命名结构体,访问结构体变量 w 中的成员时,使用到了中间的结构体变量。
8 type Point struct { 9 X int 10 Y int 11 } 12 13 type Circle struct { 14 Center Point 15 Radius int 16 } 17 18 type Wheel struct { 19 Circle Circle 20 Spokes int 21 } 22 23 func main() { 24 var w Wheel 25 w.Circle.Center.X = 2 26 w.Circle.Center.Y = 3 27 w.Circle.Radius = 5 28 w.Spokes = 10 29 fmt.Printf("%#v\n", w) 30 }
在 Go 中,可以定义不带名称的结构体成员,只需要指定类型即可;这种结构体成员称作匿名成员。这个结构体成员必须是一个命名类型或者一个指向命名类型的指针。
8 type Point struct { 9 X int 10 Y int 11 } 12 13 type Circle struct { 14 Point 15 Radius int 16 } 17 18 type Wheel struct { 19 Circle 20 Spokes int 21 } 22 23 func main() { 24 var ww Wheel 25 ww.X = 2 26 ww.Y = 3 27 ww.Radius = 5 28 ww.Spokes = 10 29 fmt.Printf("%#v\n", ww) 30 31 var w Wheel 32 w.Circle.Point.X = 2 33 w.Circle.Point.Y = 3 34 w.Circle.Radius = 5 35 w.Spokes = 10 36 fmt.Printf("%#v\n", w) 37 38 }
Circle 中有一个匿名成员 Point,Wheel 中有一个匿名成员 Circle。结构体变量 ww 可直接访问到需要的成员变量,而不需要指定一大串中间变量。结构体变量 w 的访问成员的方式也是正确,可以看到这种方式相对繁琐了不少。
运行结果如下
b. 嵌套结构体的初始化
与前面一致。一种是按照顺序初始化,另一种是使用成员变量的名称与值进行初始化。
23 func main() { 24 w := Wheel{Circle{Point{3, 4}, 5}, 10} 25 fmt.Printf("%v\n", w) 26 27 ww := Wheel{ 28 Circle: Circle{ 29 Point: Point{ 30 X: 3, 31 Y: 4, 32 }, 33 Radius: 5, 34 }, 35 Spokes: 10, 36 } 37 fmt.Printf("%v\n", ww) 38 }
两种方式打印结果一致
第31行,33行,35行最后面的逗号都需要带上,不然会编译报错。
c. 匿名成员不一定是结构体类型,任何命名类型或者指向命名类型的指针都可以。
结构体类型 MyType 中的成员 Ages 和 Weight 都是自定义的类型,Name 是内置类型,ContactInfo 是嵌套的结构体类型。
23 type Ages int 24 type Weight float64 25 26 type ContactInfo struct { 27 Addr string 28 PhoneNum string 29 } 30 31 type MyType struct { 32 Name string 33 Ages 34 Weight 35 ContactInfo 36 } 37 38 func main() { 39 var t1 MyType 40 t1.Ages = 20 41 t1.Weight = 55 42 t1.Name = "Bob" 43 t1.Addr = "BeiJing" 44 t1.PhoneNum = "18888888888" 45 fmt.Printf("%v\n", t1) 46 }
打印结果如下
以快捷方式访问匿名成员的内部变量同样适用于访问匿名成员的内部方法。因此,外围的结构体类型获取的不仅仅是匿名成员的内部变量,还有相关的方法。