附加到具有相同底层数组的两个切片,为什么结果?

Appending to two slice with same underlying array, why the result?

这是一些 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]"
}

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

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

Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array

If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array

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

  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 时犯了很大的错误。请告诉我哪里错了。看来我的推理与 slice 的行为是一致的。 (我知道附加到容量不足的切片也会重新分配一个底层数组,但不知道如何将其放入其中)。

让我们一步步分析这个程序:

s1 := []int{1, 2}  // s1 -> [1,2]
s2 := s1           // s1, s2 -> [1,2]

接下来的操作是:

s2 = append(s2, 3)

如果基础数组的容量不足,这可能分配一个新的支持数组。在这种情况下,它会这样做:

s1 -> [1,2]
s2 -> [1,2,3]

然后incr(s1)会追加一个新元素到s1,增加值,但是得到的slice不会赋值给main中的s1,所以还是:

s1 -> [1,2]
s2 -> [1,2,3]

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

s1 -> [1,2]
s2 -> [2,3,4]

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