Go 的 array 是定长的;slice 是不定长的。两者长得非常像:
- Array:类型表达为
[N]T
,N 是长度,T 是类型,如[5]int
- Slice:类型表达为
[]T
,T 是类型,如[]int
构建
//// Array: [N]T
// l1: 声明但不赋值。此时每个元素都是 zero value
var l1 [5]int
// l2: 声明且赋值
var l2 [5]int = [5]int{4, 2, 1, 3, 5}
// l3 及 l4: 自动推断类型
var l3 = [5]int{4, 2, 1, 3, 5}
l4 := [5]int{4, 2, 1, 3, 5}
// l5: 自动推断类型及长度([5]int)
l5 := [...]int{4, 2, 1, 3, 5}
//// Slice: []T
// s1: 声明但不赋值,空 slice
var s1 []int
// s2: 声明且赋值
var s2 []int = []int{4, 2, 1, 3, 5}
// s3 及 s4:自动推断类型
var s3 = []int{4, 2, 1, 3, 5}
s4 := []int{4, 2, 1, 3, 5}
// s5: 用 make() 构建
// func make([]T, len, cap) []T
// func make([]T, len) []T
s5 := make([]int, 5)
Array 实现
Array 在 Go 的设计中是 值类型的。在 Go blog 中这么描述的:
Go's arrays are values. An array variable denotes the entire array; it is not a pointer to the first array element (as would be the case in C). This means that when you assign or pass around an array value you will make a copy of its contents. (To avoid the copy you could pass a pointer to the array, but then that's a pointer to an array, not an array.) One way to think about arrays is as a sort of struct but with indexed rather than named fields: a fixed-size composite value.
一个 array 变量赋值给其他变量时,值会被拷贝:
a := [...]int{1, 1, 1}
b := a
a[0] = 0
fmt.Println(a, b)
[0 1 1] [1 1 1]
可以修改 array 中的元素值,但是无法改变 array 大小(比如 push / pop)。
Slice 实现
Slice 支持以下标索引形式读写元素值。但是对超出长度部分的写入会报 runtime error:
// len(b) 为 2
b := make([]int, 2, 4)
b[0] = 1
b[1] = 2
b[2] = 3 // runtime error
Slice 的空值是 nil。cap()
len()
在此情况下会返回 0。
Slice 本质上是指向某块内存区域的结构信息:
Slice 支持 slicing 操作,slice 出来的结果仍然是 slice,能被读取但是无法被写入:
b := []int{1, 2, 3, 4}
c := b[2:] // c: {3, 4}
b[2:] = []int{5, 6} // compile error
Slicing 操作是不复制 slice 中元素的,它生成的 slice 跟原 slice 指向同块区域。因此注意 slicing 后的 len 及 cap:
b := make([]int, 2, 4)
b[0] = 1
b[1] = 2
c := b[2:]
fmt.Printf("len(b): %v, cap(b): %v\n", len(b), cap(b))
fmt.Printf("len(c): %v, cap(c): %v\n", len(c), cap(c))
len(b): 2, cap(b): 4
len(c): 0, cap(c): 2
对 slice 追加数据,需要通过 append()
函数。因为 slice 并不实际拥有底下的元素,因此设计上:
- 并不是
slice.append()
, - 而是
slice = append(slice, V)
因为如果 append 之后超出了原有的 capacity 时,go 会使 capacity 增大:
b := make([]int, 4)
fmt.Printf("len(b): %v, cap(b): %v\n", len(b), cap(b))
b = append(b, 0)
fmt.Printf("len(b): %v, cap(b): %v\n", len(b), cap(b))
len(b): 4, cap(b): 4
len(b): 5, cap(b): 8
当 append 后原有的位置内存不足时,会把数据拷贝到另外一处内存;此时会生成一个新的 slice 对象(ptr
len
cap
均有变化),赋值给原有的变量 slice
(可能观察到的 slice 的指针地址是没有变化的,有可能是 Go 把老的对象 GC 了,或者是做了优化)。
Slice 合并
对于两个同类型的 slice s1
s2
,如果想把 s2
的元素全部追加到 s1
后面,可以这样写:
s := append(s1, s2...)
因为 append()
是个 variadic function。并且这样没有性能问题,go 在实现上并不会把参数一个个 push 进栈里面,而是类似传一个数组进来。