这是 Go 语言之旅的笔记 P2, 包括了指针、结构体、数组、切片、映射和闭包等内容。
指针
指针, 指针保存了值的地址:
1 | // 指针声明 |
结构体
结构体 struct,即一组字段 field:
1 | // 定义 |
这里我们第一次看到了 type
,可以点击这篇文章了解更多。
使用指针访问结构体:
1 | // 访问结构体,需要结构提指针 |
注意:结构体内部的变量名的大小写,会影响外部访问结构体内部的权限。
结构体占用内存的大小是如何计算的?在 Go 中可以使用 sizeof 函数查看对象占用的对象类型。
1 | // 占用 8 字节 |
其实 go 语言中的 string 是一个结构体:
1 | type string struct |
甚至 slice 也是一个结构体,类似这样:
1 | type slice struct { |
数组
1 | // 声明形式 [n]T |
切片
切片为数组提供动态大小, 在实践中,切片比数组更常用:
1 | // 切片与 Python Pandas 中 iloc 的语法类似 |
使用 make 创建切片, make 函数会分配一个元素为零值的数组并返回一个引用了它的切片, 这也是在 Go 中使用动态数组的方式:
1 | // 使用 make |
切片的切片:
1 | // 类似操作 dataframe |
向切片追加元素:
1 | var s []int |
for 循环的 range 形式, 类似于 Python 中的 enumrate,但它是一个关键字而不是函数:
映射
1 | // 每次迭代都会返回两个值 |
映射,类似 Python 中的字典,但需要规定 Key 和 Value 的类型,假设值是 struct,则需提前定义:
1 | // 声明结构体,用来存值 |
获取、编辑映射内部的值:
1 | m := make(map[string]int) |
闭包
函数值,Go 中函数也可以被传到另一个函数中:
1 | // compute 函数接收另外一个函数 |
函数的闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
来一步一步理解 Go 中的闭包: 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57// 定义一个普通的函数 send 并调用
func send(message string){
fmt.Println(message)
}
send("hi, scott")
// 定义一个匿名版本的 send 函数并立即调用
// 注意这个函数并没有函数名字
func (message string){
fmt.Println(message)
}("hi, scott")
// 定义一个函数,返回一个函数
/*
func give_me_a_func() |func(string)| <---- 这里规定了返回值
即为一个接收 string 的函数
*/
func give_me_a_func() func(string) {
return func(message string){
fmt.Println(message)
}
}
// 可以把 give_me_a_func() 传给另外一个函数
send_func := give_me_a_func()
send_func("hi scott")
// 理解了上面函数的工作方式后,我们来介绍闭包
// 先定义一个函数, 它返回一个函数(return int)
func incrementor() func() int{
i := 0
return func() int {
i++
return i
}
}
// 现在定义另外一个变量来存储 incrementor 返回的函数
next := incrementor()
// 打印每次调用 next() 返回的值
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3
// 分析
func incrementor() func() int{
i := 0
return func() int {
// 这里 i 看起来应该无法工作的
// 因为 i 是定义在 incrementor 中的
// 但闭包拥有其被创建环境下的作用域
i++
return i
}
}