Golang函数内slice进行append时不影响外部值问题
这个标题…在看完这篇文章之后应该就能知道,这么说是不正确的,虽然看起来好像是这样的,哈哈哈哈
问题引入:下面这样一段代码会输出什么?
package main
import "fmt"
func main() {
arr := make([]int, 3, 4)
arr[0] = 0
arr[1] = 1
arr[2] = 2
fmt.Printf("main before: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
ap1(arr)
fmt.Printf("main ap1 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
}
func ap1(arr []int) {
fmt.Printf("ap1 before: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
arr[0] = 11
arr = append(arr, 111)
fmt.Printf("ap1 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
输出:
main before: len: 3 cap:4 data:[0 1 2]
ap1 before: len: 3 cap:4 data:[0 1 2]
ap1 after: len: 4 cap:4 data:[11 1 2 111]
main ap1 after: len: 3 cap:4 data:[11 1 2]
可以发现,函数内append之后,外部实参切片的值真的没改变,但到底是没改变,还是 “看不到” 呢? 在Golang参数传递:值传递那篇文章中有说过,slice是值传递,但是slice本身是一个结构体,包含一个指针,指向底层数组,将slice按值传递给函数,在函数内对其修改,影响将会传递到函数外,因为底层的数组被修改了。
type slice struct {
array unsafe.Pointer
len int
cap int
}
可以打印一下地址值看看:
func main() {
arr := make([]int, 3, 4)
arr[0] = 0
arr[1] = 1
arr[2] = 2
fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
ap1(arr)
fmt.Printf("main ap1 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
}
func ap1(arr []int) {
fmt.Printf("ap1 before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
arr[0] = 11
arr = append(arr, 111)
fmt.Printf("ap1 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}
输出: 既然都是指向的同一个数组,为什么实参slice输出的内容有些被修改了,有些没被修改呢?一切还要从slice的len这个属性说起,就算cap再大,它也只能感知到len范围内的内容,之外的感知不到的…画图说明如下: ok,接下来再做几个实验:
func main() {
arr := make([]int, 3, 4)
arr[0] = 0
arr[1] = 1
arr[2] = 2
fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
ap2(arr)
fmt.Printf("main ap2 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
ap3(arr)
fmt.Printf("main ap3 after: len: %d cap:%d data:%+v\n\n", len(arr), cap(arr), arr)
ap4(arr)
fmt.Printf("main ap4 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func ap2(arr []int) {
fmt.Printf("ap2 before: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
arr = append(arr, 222)
arr[0] = 22
fmt.Printf("ap2 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func ap3(arr []int) {
fmt.Printf("ap3 before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
arr = append(arr, 333,333)
arr[0] = 33
fmt.Printf("ap3 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}
func ap4(arr []int) {
fmt.Printf("ap4 before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
arr[0] = 44
arr = append(arr, 444,444)
fmt.Printf("ap4 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}
输出:
为什么ap3中,向切片中append了两个元素之后,再修改其内容就影响不到实参原切片的内容了呢?
这是因为切片底层的扩容机制,当对切片进行append()操作,若是元素数量超过原有切片的容量,将会使得切片扩容,这就是问题所在。扩容后的切片,本质上是产生一个新的底层数组,并把旧数组的值复制进去,然后旧数组被回收掉。如果在函数内对切片添加元素导致扩容,会导致元素内的切片指向一个新的数组,但是函数外的切片仍然指向原来旧的数组,则将会导致影响无法传递到函数外。如果希望函数内对切片扩容作用于函数外,就需要以指针形式传递切片。
源码:src/runtime/slice.go 中的 growslice 函数中的核心部分。
func growslice(et *_type, old slice, cap int) slice {
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
}
指针形式传递切片:【注意切片指针类型寻址的时候要(*指针)[index] ,因为**[]优先级比较高**】
func main() {
arr := make([]int, 3, 4)
arr[0] = 0
arr[1] = 1
arr[2] = 2
fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
ap5(&arr)
fmt.Printf("main ap5 after: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
}
func ap5(arr *[]int) {
fmt.Printf("ap5 before: len: %d cap:%d data:%+v, pointer addr:%p, arr[0] addr:%p\n", len(*arr), cap(*arr), *arr, &(arr), &(*arr)[0])
*arr = append(*arr, 555,555)
(*arr)[0] = 55
fmt.Printf("ap5 after: len: %d cap:%d data:%+v, pointer addr:%p, arr[0] addr:%p\n", len(*arr), cap(*arr), *arr, &(*arr), &(*arr)[0])
}
可以看到,在函数内部对slice扩容之后,形参和实参的底层数组都换到了同一个地址 再做个小实验,前面说slice只能感知到len范围内的内容,之外的感知不到,那要是把之外的换到之内呢?答案是肯定的
func main() {
arr := make([]int, 3, 4)
arr[0] = 0
arr[1] = 1
arr[2] = 2
fmt.Printf("main before: len: %d cap:%d data:%+v, slice addr:%p, arr[0] addr:%p\n", len(arr), cap(arr), arr, &arr, &arr[0])
reverse1(arr)
fmt.Printf("main reverse1 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
reverse2(arr)
fmt.Printf("main reverse2 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func reverse1(arr []int) {
fmt.Printf("reverse1 before: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
arr = append(arr, 111)
for i, j := 0, len(arr) - 1; i < j; i++ {
j = len(arr) - (i + 1)
arr[i], arr[j] = arr[j], arr[i]
}
fmt.Printf("reverse1 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
func reverse2(arr []int) {
fmt.Printf("reverse2 before: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
arr = append(arr, 222,22)
for i, j := 0, len(arr) - 1; i < j; i++ {
j = len(arr) - (i + 1)
arr[i], arr[j] = arr[j], arr[i]
}
fmt.Printf("reverse2 after: len: %d cap:%d data:%+v\n", len(arr), cap(arr), arr)
}
再来看点切片的其他知识吧,切片可以用上面实验中用的make的方式生成,也可以从数组中切,那切的话又有什么问题呢?
func main() {
s := make([]int, 2, 10)
fmt.Printf("s: %v, s[1] addr: %p\n", s, &s[1])
b := s[1:3:4]
fmt.Printf("b: %v, b[0] addr: %p\n", b, &b[0])
c := append(b, 2)
fmt.Println("after c := append(b,2):")
fmt.Println("s:", s)
fmt.Println("b:", b)
fmt.Printf("c: %v, len: %v, cap:%v, c[0] addr: %p\n", c, len(c), cap(c), &c[0])
fmt.Println("由于切片d长度为4,超出b的容量,go会给切片d分配新内存,以后对s的操作都与d无关")
d := append(b, 4, 5)
fmt.Println("after d := append(b,4,5):")
fmt.Println("s:", s)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Printf("d: %v, len: %v, cap:%v, d[0] addr:%p\n", d, len(d), cap(d), &d[0])
s = append(s, 3, 3)
fmt.Println("after s = append(s,3,3):")
fmt.Println("s:", s)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("d:", d)
b = append(b, 6)
fmt.Println("after b = append(b, 6):")
fmt.Println("s:", s)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("d:", d)
fmt.Println("由于切片b长度变为4,超出b的容量,go会给切片b分配新内存,以后对b的操作都与基于b的切片无关")
b = append(b, 7)
fmt.Println("after b = append(b, 7):")
fmt.Println("s:", s)
fmt.Printf("b: %v, b[0] addr: %p\n", b, &b[0])
fmt.Println("c:", c)
fmt.Println("d:", d)
b[0] = 8
fmt.Println("after b[0] = 8:")
fmt.Println("s:", s)
fmt.Println("b:", b)
fmt.Println("c:", c)
fmt.Println("d:", d)
}
ps:关于copy函数:
copy()将切片s的内容复制给切片s1:返回复制的元素数 若元素不够,则只拷贝一部分; s和s1数据空间相互独立,相互不影响
s := []int{1,2}
s1 := make([]int,len(s)*2,cap(s)*3)
copy(s1,s)
fmt.Println(len(s),cap(s),s)
fmt.Println(len(s1),cap(s1),s1)
2 2 [1 2]
4 6 [1 2 0 0]
参考
|