Golang append() 方法的问题

package main

import "fmt"

func main()  {
    x := make([]int,0,10)
    x = append(x, 1,2,3)
    y := append(x,4)
    z := append(x,5)
    fmt.Println(x)
    fmt.Println(y)
    fmt.Println(z)
}

为什么 y 输出的也是 [1 2 3 5] 呢?append() 不是拷贝 x 的值么,那怎么 zy 也覆盖了

阅读 2.6k
1 个回答

https://pkg.go.dev/builtin#ap...

The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself:

简而言之,如果 append 的 slice 有足够容量,就不重新分配内存。只有容量不足的时候才会重新分配。

参考这篇 go 团队的博客 把 slices 说的很详细了。


重新审题。

你的问题是为什么 append(x,5) 之后 y 也跟着变了,要理解这一点必须理解 slice 类型是怎么回事。

上面链接里 Go 官方团队是这么说的:

It’s important to understand that even though a slice contains a pointer, it is itself a value. Under the covers, it is a struct value holding a pointer and a length. It is not a pointer to a struct.

slice 是一个包含指针的结构值,保存了指针和长度。 slice 是 而不是 引用 理解这一点很重要。

再看你的代码。

x = append(x, 1,2,3)
y = append(x, 4)
z = append(x, 5)

我们一句一句解读。

x = append(x, 1, 2, 3)

x 看作一个结构值 struct { underlying: *[cap]int, len: 0 },已知cap足够,不重新分配底层数组,这一句 append 调用做的事情就是:

arg = x 
// x 传入 append 时经历了一次复制
// arg 和 x 共享底层数组 

// append 的业务逻辑,增加长度,直接在底层数组上赋值
arg.underlying[x.len] = 1 
arg.len++
arg.underlying[x.len] = 2
arg.len++
arg.underlying[x.len] = 3
arg.len++ 

x = arg
// x.len = 3
// underlying = [1,2,3]

然后看这一句。

y = append(x, 4)

经过第一个 append,现在的xstruct{ underlying: [cap]int, len: 3}cap依然足够。所以append的实际行为如下。

arg = x
// x 传入 append 时经历了一次复制
// arg 和 x 共享底层数组 

// arg.len = 3
arg.underlying[arg.len] = 4
arg.len++
// arg.len = 4

y = arg
// y = struct { underlying: *[cap]int, len: 4}
// y.underlying = [1,2,3,4]

最后是 z = append(x, 5)

arg = x
// x 传入 append 时经历了一次复制
// arg 和 x 共享底层数组 

// arg.len = 3
// 所以修改的是共同的底层数组的第4个元素(下标从0开始数)
arg.underlying[arg.len] = 5
arg.len++

z = arg
// z.len=4
// z.underlying=[1,2,3,5]

因为 x/y/z 共享底层数组,appendyz处调用时,根据x的长度,修改的都是第4个元素,造成执行append(x, 5)之后y的第4个元素也变成了5。

y = append(x, 4) 没有修改 x 的原因是,修改了但没有表现出来。因为 x.len 指明 x 的长度只有 3 ,虽然底层数组第 4 个元素已经被修改成了 4 ,但打印 x 的时候是不会打印出 4 的。

同理,z = append(x, 5) 修改的底层数组是和x共享的,在执行了append(x, 5)之后其实x的底层数组第4个元素已经变成了5,只是x里保存的长度还是3,所以你没法看到它的第4个元素。但yz共享底层数组,而且y的长度是4,所以你能看到y的第4个元素变成了5。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题