最近在开发过程中遇到这样一个问题:一个函数接收slice作为参数,然后会append数据,但是append的数据对调用者不可见,具体看下面代码
func TestSlice(t *testing.T) {
sli := make([]int, 0, 2)
sli = append(sli, 1)
sliceAppend(sli)
fmt.Printf("%v\n", sli) //[0]
}
func sliceAppend(ans []int) {
ans = append(ans, 2)//对调用者不可见
ans[0] = 0//对调用者可见
}
这里为什么会输出[0],而不是[0, 2]呢?
要想搞清楚这个问题,先来看看slice底层结构
type slice struct {
array unsafe.Pointer //底层数组指针
len int //长度
cap int //容量
}
go语言都是值传递,所以sliceAppend函数的入参ans其实是sli的副本,但是ans和sli是指向相同的底层数据的,所以ans[0] = 0
会生效,但是为什么ans = append(ans, 2)
会对TestSlice不可见呢?
因为slice是通过len来控制底层数组的可见范围的,调用完sliceAppend后sli的len并没有变,仍是1,所以是只能访问到0,不能访问2的。可以通过下面代码来加深理解
func TestSliceAddr(t *testing.T) {
sli := make([]int, 0, 2)
sli = append(sli, 1)
fmt.Printf("sli[0] addr=:%p\n", &sli[0])
sliceAppend(sli)
fmt.Printf("%v\n", sli)
header := *(*uintptr)(unsafe.Pointer(&sli))
fmt.Printf("sli len = %v\n", len(sli))//1
fmt.Printf("sli[0] = %v\n", *(*int32)(unsafe.Pointer(header + 0)))//0
//强制访问sli[1]
fmt.Printf("sli[1] = %v\n", *(*int32)(unsafe.Pointer(header + 8)))//2
}
func sliceAppend(ans []int) {
fmt.Printf("befor reallomc ans[0] addr=:%p\n", &ans[0])//和sli共享底层数组,所以 &ans[0] == &sli[0]
ans[0] = 0
ans = append(ans, 2)
fmt.Printf("befor reallomc ans[0] addr=:%p\n", &ans[0])//底层数组仍没有改变,所以 &ans[0] == &sli[0]
ans = append(ans, 3)
fmt.Printf("after reallomc ans[0] addr=:%p\n", &ans[0])//append 3之前,len == capacity,所以需要进行重分配,导致底层数组发生了变化,所以&ans[0] != &sli[0]
/**
output:
sli[0] addr=:0xc0000a61d0
befor reallomc ans[0] addr=:0xc0000a61d0
befor reallomc ans[0] addr=:0xc0000a61d0
befor reallomc ans[0] addr=:0xc0000a61d8
after reallomc ans[0] addr=:0xc0000da0a0
[0]
sli len = 1
sli[0] = 0
sli[1] = 2
*/
}
如果想要实现sliceAppend里面append数据对调用者可见可以使用下面的方式
func sliceAppend2(ans []int) []int{
ans[0] = 0
ans = append(ans, 2)
ans = append(ans, 3)
return ans
}
//传指针
func sliceAppend3(ans *[]int) {
(*ans)[0] = 0
*ans = append(*ans, 2)
*ans = append(*ans, 3)
}
参考链接:
https://stackoverflow.com/questions/20195296/golang-append-an-item-to-a-slice