go slice

介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func Reverse(s []int) {
s = append(s, 999)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
}

func main() {
s := []int{1, 2, 3}
Reverse(s)
fmt.Println(s) // [1 2 3]

var a []int
for i := 1; i <= 3; i++ {
a = append(a, i)
}
Reverse(a)
fmt.Println(a) // [999 3 2]
}

在传入Reverse2函数之前,切片的cap不一致,这导致了在函数中append后,第一个函数中,s指向第一个元素的地址发生了变化,修改的不再是函数外的切片。
第二个函数经过3次append,cap已经是4了,这样即使在函数中再append一次,内存也不会发生再次分配,这样修改的还是函数外的切片。
为了验证这一点,我们可以把地址都打印出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func Reverse(s []int) {
s = append(s, 999)
for i, j := 0, len(s)-1; i < j; i++ {
j = len(s) - (i + 1)
s[i], s[j] = s[j], s[i]
}
fmt.Printf("[in function] s:%p, first element:%p, len:%d, cap:%d\n",&s,s,len(s),cap(s))
}

func main() {
s := []int{1, 2, 3}
fmt.Printf("[main] s:%p, first element:%p, len:%d, cap:%d\n",&s,s,len(s),cap(s))
Reverse(s)
fmt.Println(s) // [1 2 3]

var a []int
for i := 1; i <= 3; i++ {
a = append(a, i)
}
fmt.Printf("[main] a:%p, first element:%p, len:%d, cap:%d\n",&a,a,len(a),cap(a))
Reverse(a)
fmt.Println(a) // [999 3 2]
}

打印如下:

1
2
3
4
5
6
7
➜  [/Users/rootliu/go/src/6.824/demo] git:(master) ✗ go run demo.go
[main] s:0xc00000a060, first element:0xc000018160, len:3, cap:3
[in function] s:0xc00000a0a0, first element:0xc00001a0f0, len:4, cap:6
[1 2 3]
[main] a:0xc00000a100, first element:0xc0000181a0, len:3, cap:4
[in function] s:0xc00000a140, first element:0xc0000181a0, len:4, cap:4
[999 3 2]

可以得出结论:
1、函数参数切片时值传递,因为切片在函数中的地址跟外层不一样
2、发生内存扩充时,cap变了,指向第一个元素的地址也发生了变化

参考文献

理解 Go 的 Array 和 Slice
Print address of slice in golang