关于Go中slice作为函数参数丢失修改的问题

众所周知,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作为返回值吧

おすすめ

転載: juejin.im/post/7076346965507702791
おすすめ