将两个切片追加到具有相同底层数组的情况下,结果是什么?

huangapple go评论87阅读模式
英文:

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 的两点:

切片保存对底层数组的引用,如果将一个切片赋值给另一个切片,两者都引用同一个数组。

如果一个函数接受一个切片参数,并对切片中的元素进行更改,这些更改将对调用者可见,类似于将指针传递给底层数组。

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

  1. 起初,s1和s2都引用同一个底层数组[1, 2]。
  2. 在将3追加到s2之后,底层数组变为[1, 2, 3]。但是s1仍然只能看到[1, 2]。
  3. 在执行incr(s1)之后,s1被追加了0,并且所有的元素都加1,结果s1变为[2, 3, 1]。追加操作也改变了底层数组,所以s2现在看到的是[2, 3, 1]。
  4. 在执行incr(s2)之后,s2被追加了0,并且所有的元素都加1,结果s2变为[3, 4, 2, 1]。递增操作也影响了底层数组,所以现在s1看到的是[3, 4, 2]。
  5. 因此,打印的结果应该是[3, 4, 2][3, 4, 2, 1]。

显然,我在理解Go中的切片方面犯了一个很大的错误。请告诉我我错在哪里。我的推理似乎符合切片的行为。(我知道向容量不足的切片追加元素也会重新分配底层数组,但不知道如何将其融入到这个例子中)。

英文:

Here is a code snippet from some Go book.

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]"
}

I don't understand why the result is "[1, 2][2, 3, 4]".

Based on two points from <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

Here is what I imagined should happen:

  1. At first, both s1 and s2's have the same underlying array [1, 2]
  2. After appending 3 to s2, the underlying array changed to [1, 2, 3]. But s1 still only see [1, 2]
  3. After incr(s1), s1 is appended 0 and all item incremented, resulting s1 into [2, 3, 1]. The appending also changed the underlying array, so s2 now see [2, 3, 1]
  4. After incr(s2), s2 is appended 0 and all item incremented, resulting s2 into [3, 4, 2, 1]. The increment also affected underlying array, so now s1 see [3, 4, 2]
  5. So the result printed should be [3, 4, 2][3, 4, 2, 1]

I obviously have huge mistake understanding slice in Go. Please tell me where I am wrong. It seems my reasoning is in accord with slice's behavior. (I know appending to an insufficient capacity slice will also reallocate an underlying array, but don't know how to fit it into this).

答案1

得分: 4

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

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 添加一个新元素,并递增值,但是结果切片不会在 main 函数中赋值给 s1,所以仍然是:

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

incr(s2) 会做同样的操作,但是这次支持数组有足够的容量来容纳追加的零值,所以递增操作会递增值,但是新的切片从未赋值给 s2。因此,s2 仍然有 3 个元素:

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

s2 的支持数组有一个额外的元素,但是 s2 不包括该元素。

英文:

Let's analyze this program step by step:

s1 := []int{1, 2}  // s1 -&gt; [1,2]
s2 := s1           // s1, s2 -&gt; [1,2]

The next operation is:

s2 = append(s2, 3)

This may allocate a new backing array if the capacity of the underlying array is not sufficient. In this case, it will do that to give:

s1 -&gt; [1,2]
s2 -&gt; [1,2,3]

Then incr(s1) will append a new element to s1, increment values, but the resulting slice will not be assigned to s1 in main, so still:

s1 -&gt; [1,2]
s2 -&gt; [1,2,3]

incr(s2) will do the same, but this time, the backing array has the capacity to hold the appended zero, so the increment operation increments the values, but the new slice is never assigned to s2 in main. So, s2 still has 3 elements:

s1 -&gt; [1,2]
s2 -&gt; [2,3,4]

The backing array of s2 has one more element in it, but s2 does not include that element.

huangapple
  • 本文由 发表于 2021年9月17日 00:20:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/69211932.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定