众所周知,slice的数据结构如下
type slice struct {
array unsafe.Pointer
len int
cap int
}
复制代码
slice是一个引用类型,他的struct中持有一个名为array的指针,也就是数组的首地址,设这个地址为x,那么目前能通过这个slice访问到的地址空间就是x[0...n]其中n为数组的长度
那么假设现在我有一个函数
func appendToSlice(s []int) {
s = append(s, 1)
}
复制代码
对其进行操作
func main() {
s := []int{1,2}
appendToSlice(s)
fmt.Println(s)
}
复制代码
上述代码创建了一个slice,创建后的slice中:
- len和cap都为2
- 持有一个array指针,指向数组头,这个数组占用的空间为2*int的大小(int的大小与机器字长有关)
然后调用了appendToSlice
这个函数,根据我们的直觉,向这个slice中append了一个元素,输出的结果应该是[1, 2, 1]
然而实际上,输出的结果是[1, 2]
实际上,我们对slice的append的操作的确发生了,这里slice的cap不够,需要进行扩容,这个数组会被搬迁到一个新的位置,但虽然slice是个引用类型,在appendToSlice
这个函数中s中的array指针也被赋值了,但由于slice归根结底只是那样一个struct,所以在我们传参的时候实际上传的是struct{unsafe.Pointer}
而不是我们想象的*arr
正确的操作就是传递slice的指针,也就是**arr
,通过传递一个二级指针,使得在函数中的修改能直接修改到slice中arr指针的值,所以不要被“引用类型”给骗了
func appendToSlice(s *[]int) {
*s = append(*s, 1)
}
func main() {
s := []int{1,2}
appendToSlice(&s)
fmt.Println(s) // [1, 2, 1]
}
复制代码
当然,丢失修改的情况只会发生在扩容的时候,因为只有这时会搬迁数组,使指针还停留在原来的数组从而得到不符合预期的结果。
并且,这种二级指针的做法也是不推荐的,还是老老实实将新的slice作为返回值吧