附加到具有相同基础数组的两个切片,为什么会产生结果?

Uni*_*eak -2 append go slice

这是一些 Go 书籍中的代码片段。

func incr(s []int) {
    s = append(s, 0)
    for i := range s {
        s[i]++
    }
}

func main() {
    s1 := []int{1, 2}
    s2 := s1
    s2 = append(s2, 3)

    incr(s1)
    incr(s2)

    fmt.Print(s1, s2) // "[1, 2][2, 3, 4]"
}
Run Code Online (Sandbox Code Playgroud)

我不明白为什么结果是“[1, 2][2, 3, 4]”。

基于https://golang.org/doc/ effective_go#slices 中的两点:

切片保存对基础数组的引用,如果将一个切片分配给另一个切片,则两个切片都引用同一个数组

如果函数采用切片参数,则调用者可以看到它对切片元素所做的更改,类似于将指针传递给底层数组

这是我想象应该发生的事情:

  1. 首先,s1 和 s2 都有相同的底层数组 [1, 2]
  2. 将 3 追加到 s2 后,底层数组变为 [1, 2, 3]。但 s1 仍然只看到 [1, 2]
  3. 之后incr(s1),s1 被追加 0,所有项递增,结果 s1 变为 [2, 3, 1]。附加操作还更改了底层数组,因此 s2 现在看到 [2, 3, 1]
  4. 之后incr(s2),s2 被追加 0,所有项递增,结果 s2 变为 [3, 4, 2, 1]。增量也影响了底层数组,所以现在 s1 看到 [3, 4, 2]
  5. 所以打印的结果应该是 [3, 4, 2][3, 4, 2, 1]

显然我对 Go 中 slice 的理解存在巨大错误。请告诉我哪里错了。看来我的推理与切片的行为是一致的。(我知道附加到容量不足的切片也会重新分配底层数组,但不知道如何将其放入其中)。

Bur*_*dar 5

我们来逐步分析一下这个程序:

s1 := []int{1, 2}  // s1 -> [1,2]
s2 := s1           // s1, s2 -> [1,2]
Run Code Online (Sandbox Code Playgroud)

接下来的操作是:

s2 = append(s2, 3)
Run Code Online (Sandbox Code Playgroud)

如果底层数组的容量不够,这可能会分配一个新的后备数组。在这种情况下,它将给出:

s1 -> [1,2]
s2 -> [1,2,3]
Run Code Online (Sandbox Code Playgroud)

然后incr(s1)将向 追加一个新元素s1,递增值,但生成的切片不会分配给s1main 中,所以仍然:

s1 -> [1,2]
s2 -> [1,2,3]
Run Code Online (Sandbox Code Playgroud)

incr(s2)会做同样的事情,但这一次,后备数组有能力保存附加的零,因此增量操作会递增值,但新切片永远不会分配给s2in main。所以,s2仍然有3个要素:

s1 -> [1,2]
s2 -> [2,3,4]
Run Code Online (Sandbox Code Playgroud)

的支持数组s2中还有一个元素,但s2不包含该元素。

  • 我认为这是一种令人费解的看待方式。当您将切片传递给函数时,您会传递一个具有 3 个值的结构:(ptr、len、cap)。该结构作为值而不是指针传递。该结构体中有一个指向底层数组的指针。对底层数组所做的修改对调用者来说是可见的。对结构本身的修改对调用者来说是不可见的。 (2认同)