上一篇: [1438260619759]
ToC
- Go 边看边练 -《Go 学习笔记》系列(一)- 变量、常量
- Go 边看边练 -《Go 学习笔记》系列(二)- 类型、字符串
- Go 边看边练 -《Go 学习笔记》系列(三)- 指针
- Go 边看边练 -《Go 学习笔记》系列(四)- 控制流 1
- Go 边看边练 -《Go 学习笔记》系列(五)- 控制流 2
- Go 边看边练 -《Go 学习笔记》系列(六)- 函数
- Go 边看边练 -《Go 学习笔记》系列(七)- 错误处理
- Go 边看边练 -《Go 学习笔记》系列(八)- 数组、切片
- Go 边看边练 -《Go 学习笔记》系列(九)- Map、结构体
- Go 边看边练 -《Go 学习笔记》系列(十)- 方法
- Go 边看边练 -《Go 学习笔记》系列(十一)- 表达式
- Go 边看边练 -《Go 学习笔记》系列(十二)- 接口
- Go 边看边练 -《Go 学习笔记》系列(十三)- Goroutine
- Go 边看边练 -《Go 学习笔记》系列(十四)- Channel
4.1 Array
和以往认知的数组有很大不同。
- 数组是值类型,赋值和传参会复制整个数组,而不是指针。
- 数组长度必须是常量,且是类型的组成部分。
[2]int
和[3]int
是不同类型。 - 支持 "=="、"!=" 操作符,因为内存总是被初始化过的。
- 指针数组
[n]*T
,数组指针*[n]T
。
可用复合语句初始化。
1a := [3]int{1, 2} // 未初始化元素值为 0。 2b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。 3c := [5]int{2: 100, 4:200} // 使用索引号初始化元素。 4 5d := [...]struct { 6 name string 7 age uint8 8}{ 9 {"user1", 10}, // 可省略元素类型。 10 {"user2", 20}, // 别忘了最后一行的逗号。 11}
支持多维数组。
1a := [2][3]int{{1, 2, 3}, {4, 5, 6}} 2b := [...][2]int{{1, 1}, {2, 2}, {3, 3}} // 第 2 纬度不能用 "..."。
值拷贝行为会造成性能问题,通常会建议使用 slice
,或数组指针。
内置函数 len
和 cap
都返回数组长度 (元素数量)。
1a := [2]int{} 2println(len(a), cap(a)) // 2, 2
4.2 Slice
需要说明,slice
并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
runtime.h
1struct Slice 2{ // must not move anything 3 byte* array; // actual data 4 uintgo len; // number of elements 5 uintgo cap; // allocated number of elements 6};
-
引用类型。但自身是结构体,值拷贝传递。
-
属性
len
表示可用元素数量,读写操作不能超过该限制。 -
属性
cap
表示最大扩张容量,不能超出数组限制。 -
如果
slice == nil
,那么len
、cap
结果都等于0
。data := [...]int{0, 1, 2, 3, 4, 5, 6}
slice := data[1:4:5] // [low : high : max]
创建表达式使用的是元素索引号,而非数量。
读写操作实际目标是底层数组,只需注意索引号的差别。
1data := [...]int{0, 1, 2, 3, 4, 5} 2 3s := data[2:4] 4s[0] += 100 5s[1] += 200 6 7fmt.Println(s) 8fmt.Println(data)
输出:
1[102 203] 2[0 1 102 203 4 5]
可直接创建 slice
对象,自动分配底层数组。
1s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。 2fmt.Println(s1, len(s1), cap(s1)) 3 4s2 := make([]int, 6, 8) // 使用 make 创建,指定 len 和 cap 值。 5fmt.Println(s2, len(s2), cap(s2)) 6 7s3 := make([]int, 6) // 省略 cap,相当于 cap = len。 8fmt.Println(s3, len(s3), cap(s3))
输出:
1[0 1 2 3 0 0 0 0 100] 9 9 2[0 0 0 0 0 0] 6 8 3[0 0 0 0 0 0] 6 6
使用 make
动态创建 slice
,避免了数组必须用常量做长度的麻烦。还可用指针直接访问底层数组,退化成普通数组操作。
1s := []int{0, 1, 2, 3} 2 3p := &s[2] // *int, 获取底层数组元素指针。 4*p += 100 5 6fmt.Println(s)
输出:
1[0 1 102 3]
至于 [][]T
,是指元素类型为 []T
。
1data := [][]int{ 2 []int{1, 2, 3}, 3 []int{100, 200}, 4 []int{11, 22, 33, 44}, 5}
可直接修改 struct array
/slice
成员。
1d := [5]struct { 2 x int 3}{} 4 5s := d[:] 6 7d[1].x = 10 8s[2].x = 20 9 10fmt.Println(d) 11fmt.Printf("%p, %p\n", &d, &d[0])
输出:
[{0} {10} {20} {0} {0}]
0x20819c180, 0x20819c180
4.2.1 reslice
所谓 reslice
,是基于已有 slice
创建新 slice
对象,以便在 cap
允许范围内调整属性。
1s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 2 3s1 := s[2:5] // [2 3 4] 4s2 := s1[2:6:7] // [4 5 6 7] 5s3 := s2[3:6] // Error
新对象依旧指向原底层数组。
1s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 2 3s1 := s[2:5] // [2 3 4] 4s1[2] = 100 5 6s2 := s1[2:6] // [100 5 6 7] 7s2[3] = 200 8 9fmt.Println(s)
输出:
1[0 1 2 3 100 5 6 200 8 9]
4.2.2 append
向 slice
尾部添加数据,返回新的 slice
对象。
1s := make([]int, 0, 5) 2fmt.Printf("%p\n", &s) 3 4s2 := append(s, 1) 5fmt.Printf("%p\n", &s2) 6 7fmt.Println(s, s2)
输出:
10x210230000 20x210230040 3[] [1]
简单点说,就是在 array[slice.high]
写数据。
1data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 2 3s := data[:3] 4s2 := append(s, 100, 200) // 添加多个值。 5 6fmt.Println(data) 7fmt.Println(s) 8fmt.Println(s2)
输出:
1[0 1 2 100 200 5 6 7 8 9] 2[0 1 2] 3[0 1 2 100 200]
一旦超出原 slice.cap
限制,就会重新分配底层数组,即便原数组并未填满。
1data := [...]int{0, 1, 2, 3, 4, 10: 0} 2s := data[:2:3] 3 4s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。 5 6fmt.Println(s, data) // 重新分配底层数组,与原数组无关。 7fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
输出:
1[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0] 20x20819c180 0x20817c0c0
从输出结果可以看出,append
后的 s
重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过 s.cap
限制,也就不会重新分配。
通常以 2
倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len
属性,改用索引号进行操作。及时释放不再使用的 slice
对象,避免持有过期数组,造成 GC
无法回收。
1s := make([]int, 0, 1) 2c := cap(s) 3 4for i := 0; i < 50; i++ { 5 s = append(s, i) 6 if n := cap(s); n > c { 7 fmt.Printf("cap: %d -> %d\n", c, n) 8 c = n 9 } 10}
输出:
1cap: 1 -> 2 2cap: 2 -> 4 3cap: 4 -> 8 4cap: 8 -> 16 5cap: 16 -> 32 6cap: 32 -> 64
4.2.3 copy
函数 copy
在两个 slice
间复制数据,复制长度以 len
小的为准。两个 slice
可指向同一底层数组,允许元素区间重叠。
应及时将所需数据 copy
到较小的 slice
,以便释放超大号底层数组内存。
下一篇: [1438596722873]
- 本系列是基于雨痕的《Go 学习笔记》(第四版)整理汇编而成,非常感谢雨痕的辛勤付出与分享!
- 转载请注明:文章转载自:黑客与画家的社区 [http://symphony.b3log.org]
- 如果你觉得本章节做得不错,请在下面打赏一下吧~
社区小贴士
- 关注标签 [golang] 可以方便查看 Go 相关帖子
- 关注标签 [Go 学习笔记] 可以方便查看本系列
- 关注作者后如有新帖将会收到通知
该文章同步自 黑客派