结构体类型的定义

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

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

结构体变量的创建

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

package main

import "fmt"

type Course struct {
    price int
    name string
    url string
}

func main() {
    var c Course = Course {
        price: 100,
        name: "scrapy分布式爬虫",
        url: "",  // 注意这里的逗号不能少
    }
    fmt.Printf("%+v\n", c)
}

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

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

package main

import "fmt"

type Course struct {
  price int
  name string
  url string
}

func main() {
    var c Course = Course {100, "scrapy分布式爬虫", ""}
    fmt.Printf("%+v\n", c)
}

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

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

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

var c *Course = new(Course)

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

var c Course

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

var c1 Course = Course{}
var c2 Course
var c3 *Course = new(Course)

零值结构体和 nil 结构体

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

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

结构体的拷贝

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

package main

import "fmt"

type Course struct {
  price int
  name string
  url string
}


func main() {
    var c1 Course = Course {50, "scrapy分布式爬虫", ""}
    var c2 Course = c1
    fmt.Printf("%+v\n", c1)
    fmt.Printf("%+v\n", c2)
    c1.price = 100
    fmt.Printf("%+v\n", c1)
    fmt.Printf("%+v\n", c2)

    var c3 *Course = &Course{50, "scrapy分布式爬虫", ""}
    var c4 *Course = c3
    fmt.Printf("%+v\n", c3)
    fmt.Printf("%+v\n", c4)
    c3.price = 100
    fmt.Printf("%+v\n", c3)
    fmt.Printf("%+v\n", c4)
}

slice的结构体

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

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

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

字符串头的结构体

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

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

map的结构体

type hmap struct {
  count int
  ...
  buckets unsafe.Pointer  // hash桶地址
  ...
}

解释一下下面的情况

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

package main

import "fmt"
import "unsafe"

type ArrayStruct struct {
    value [10]int
}

type SliceStruct struct {
    value []int
}

func main() {
    var as = ArrayStruct{[...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
    var ss = SliceStruct{[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}}
    fmt.Println(unsafe.Sizeof(as), unsafe.Sizeof(ss))
}

结构体的参数传递

结构体是值传递

package main

import "fmt"

type Course struct {
    price int
    name string
    url string
}

func changeCourse(c Course){
    c.price = 200
}

func main() {
    var c Course = Course {
        price: 100,
        name: "scrapy分布式爬虫",
        url: "",  // 注意这里的逗号不能少
    }
    changeCourse(c)
    fmt.Println(c.price)
}