8.1.1 结构体类型的定义

  1. type Course struct {
  2. price int
  3. name string
  4. url string
  5. }

Course结构体内部有三个变量,分别是价格、课程名、url。特别需要注意是结构体内部变量的大小写,首字母大写是公开变量,首字母小写是内部变量,分别相当于类成员变量的 Public 和 Private 类别。内部变量只有属于同一个 package(简单理解就是同一个目录)的代码才能直接访问。

8.1.2 结构体变量的创建

创建一个结构体变量有多种形式,我们先看结构体变量最常见的创建形式

  1. package main
  2. import "fmt"
  3. type Course struct {
  4. price int
  5. name string
  6. url string
  7. }
  8. func main() {
  9. var c Course = Course {
  10. price: 100,
  11. name: "scrapy分布式爬虫",
  12. url: "", // 注意这里的逗号不能少
  13. }
  14. fmt.Printf("%+v\n", c)
  15. }

通过显示指定结构体内部字段的名称和初始值来初始化结构体,可以只指定部分字段的初值,甚至可以一个字段都不指定,那些没有指定初值的字段会自动初始化为相应类型的「零值」。这种形式我们称之为 「KV 形式」。

结构体的第二种创建形式是不指定字段名称来顺序字段初始化,需要显示提供所有字段的初值,一个都不能少。这种形式称之为「顺序形式」。

  1. package main
  2. import "fmt"
  3. type Course struct {
  4. price int
  5. name string
  6. url string
  7. }
  8. func main() {
  9. var c Course = Course {100, "scrapy分布式爬虫", ""}
  10. fmt.Printf("%+v\n", c)
  11. }

结构体变量和普通变量都有指针形式,使用取地址符就可以得到结构体的指针类型

  1. var c *Course = &Course {100, "scrapy分布式爬虫", ""}

8.1.3 使用new() 函数来创建一个「零值」结构体

  1. var c *Course = new(Course)

:::info 注意 new() 函数返回的是指针类型。下面再引入结构体变量的第四种创建形式,这种形式也是零值初始化,就数它看起来最不雅观。 :::

  1. var c Course

最后我们再将三种零值初始化形式放到一起对比观察一下

  1. var c1 Course = Course{}
  2. var c2 Course
  3. var c3 *Course = new(Course)

8.1.4 零值结构体和 nil 结构体

nil 结构体是指结构体指针变量没有指向一个实际存在的内存。这样的指针变量只会占用 1 个指针的存储空间,也就是一个机器字的内存大小。

  1. var c *Course = nil
  1. 而零值结构体是会实实在在占用内存空间的,只不过每个字段都是零值。如果结构体里面字段非常多,那么这个内存空间占用肯定也会很大

8.1.5 结构体的拷贝

结构体之间可以相互赋值,它在本质上是一次浅拷贝操作,拷贝了结构体内部的所有字段。结构体指针之间也可以相互赋值,它在本质上也是一次浅拷贝操作,不过它拷贝的仅仅是指针地址值,结构体的内容是共享的。

  1. package main
  2. import "fmt"
  3. type Course struct {
  4. price int
  5. name string
  6. url string
  7. }
  8. func main() {
  9. var c1 Course = Course {50, "scrapy分布式爬虫", ""}
  10. var c2 Course = c1
  11. fmt.Printf("%+v\n", c1)
  12. fmt.Printf("%+v\n", c2)
  13. c1.price = 100
  14. fmt.Printf("%+v\n", c1)
  15. fmt.Printf("%+v\n", c2)
  16. var c3 *Course = &Course{50, "scrapy分布式爬虫", ""}
  17. var c4 *Course = c3
  18. fmt.Printf("%+v\n", c3)
  19. fmt.Printf("%+v\n", c4)
  20. c3.price = 100
  21. fmt.Printf("%+v\n", c3)
  22. fmt.Printf("%+v\n", c4)
  23. }

8.1.6 slice的结构体

通过观察 Go 语言的底层源码,可以发现所有的 Go 语言内置的高级数据结构都是由结构体来完成的。
切片头的结构体形式如下,它在 64 位机器上将会占用 24 个字节

  1. type slice struct {
  2. array unsafe.Pointer // 底层数组的地址
  3. len int // 长度
  4. cap int // 容量
  5. }

:::info 此处解释一下slice的函数传递本质上也是值传递 :::

8.1.7 字符串头的结构体

它在 64 位机器上将会占用 16 个字节

  1. type string struct {
  2. array unsafe.Pointer // 底层数组的地址
  3. len int
  4. }

8.1.8 map的结构体

  1. type hmap struct {
  2. count int
  3. ...
  4. buckets unsafe.Pointer // hash桶地址
  5. ...
  6. }

8.1.9 解释一下下面的情况

在数组与切片章节,我们自习分析了数组与切片在内存形式上的区别。数组只有「体」,切片除了「体」之外,还有「头」部。切片的头部和内容体是分离的,使用指针关联起来。请读者尝试解释一下下面代码的输出结果

  1. package main
  2. import "fmt"
  3. import "unsafe"
  4. type ArrayStruct struct {
  5. value [10]int
  6. }
  7. type SliceStruct struct {
  8. value []int
  9. }
  10. func main() {
  11. var as = ArrayStruct{[...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
  12. var ss = SliceStruct{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
  13. fmt.Println(unsafe.Sizeof(as), unsafe.Sizeof(ss))
  14. }

8.1.10 结构体的参数传递

结构体是值传递

  1. package main
  2. import "fmt"
  3. type Course struct {
  4. price int
  5. name string
  6. url string
  7. }
  8. func changeCourse(c Course){
  9. c.price = 200
  10. }
  11. func main() {
  12. var c Course = Course {
  13. price: 100,
  14. name: "scrapy分布式爬虫",
  15. url: "", // 注意这里的逗号不能少
  16. }
  17. changeCourse(c)
  18. fmt.Println(c.price)
  19. }