英文:
Could anyone explain this strange behaviour of appending to golang slices
问题
下面的程序输出结果出乎意料。
func main(){
s:=[]int{5}
s=append(s,7)
s=append(s,9)
x:=append(s,11)
y:=append(s,12)
fmt.Println(s,x,y)
}
输出结果为:[5 7 9] [5 7 9 12] [5 7 9 12]
为什么x
的最后一个元素是12
?
英文:
The program below has unexpected output.
func main(){
s:=[]int{5}
s=append(s,7)
s=append(s,9)
x:=append(s,11)
y:=append(s,12)
fmt.Println(s,x,y)
}
output: [5 7 9] [5 7 9 12] [5 7 9 12]
Why is the last element of x
12
?
答案1
得分: 15
切片只是数组的一部分窗口,它没有具体的存储。
这意味着如果你有两个切片指向同一个数组的同一部分,这两个切片必须“包含”相同的值。
具体的情况如下:
- 当你进行第一次
append
操作时,你得到一个大小为2的新切片,它指向一个大小为2的底层数组。 - 当你进行下一次
append
操作时,你得到一个大小为3的新切片,但底层数组的大小为4(append
通常会分配比实际需要的更多的空间,以避免每次append
都需要重新分配内存)。 - 这意味着下一次的
append
操作不需要新的数组。所以x
和y
都将使用与前一个切片s
相同的底层数组。你先在这个数组的一个位置写入11
,然后写入12
,即使你得到了两个不同的切片(记住,它们只是窗口)。
你可以通过在每次append
操作后打印切片的容量来验证这一点:
fmt.Println(cap(s))
如果你想在x
和y
中有不同的值,你可以进行复制,例如:
s := []int{5}
s = append(s, 7)
s = append(s, 9)
x := make([]int,len(s))
copy(x,s)
x = append(x, 11)
y := append(s, 12)
fmt.Println(s, x, y)
另一种解决方法是强制x
切片背后的数组容量不大于所需容量(从而确保后续的两次append
操作必须使用新的数组):
s := []int{5}
s = append(s, 7)
s = append(s, 9)
s = s[0:len(s):len(s)]
x := append(s, 11)
y := append(s, 12)
fmt.Println(s, x, y)
参考:在Go中重新切片切片
英文:
A slice is only a window over part of an array, it has no specific storage.
This means that if you have two slices over the same part of an array, both slices must "contain" the same values.
Here's exactly what happens here :
- When you do the first
append
, you get a new slice of size2
over an underlying array of size2
. - When you do the next
append
, you get a new slice of size3
but the underlying array is of size4
(append
usually allocates more space than the immediately needed one so that it doesn't need to allocate at every append). - This means the next
append
doesn't need a new array. Sox
andy
both will use the same underlying array as the precedent slices
. You write11
and then12
in the same slot of this array, even if you get two different slices (remember, they're just windows).
You can check that by printing the capacity of the slice after each append :
fmt.Println(cap(s))
If you want to have different values in x
and y
, you should do a copy, for example like this :
s := []int{5}
s = append(s, 7)
s = append(s, 9)
x := make([]int,len(s))
copy(x,s)
x = append(x, 11)
y := append(s, 12)
fmt.Println(s, x, y)
Another solution here might have been to force the capacity of the array behind the s
slice to be not greater than the needed one (thus ensuring the two following append
have to use a new array) :
s := []int{5}
s = append(s, 7)
s = append(s, 9)
s = s[0:len(s):len(s)]
x := append(s, 11)
y := append(s, 12)
fmt.Println(s, x, y)
See also Re-slicing slices in Golang
答案2
得分: 7
dystroy解释得很好。我想为这个行为添加一个可视化解释。
一个切片只是数组段的描述符。它由指向数组的指针(ptr)、段的长度(len)和容量(cap)组成。
所以,代码的解释如下:
func main() {
s := []int{5}
s -> +-----+
| ptr |
|*Elem|
+-----+
| len |
|int |
+-----+
| cap |
|int |
+-----+
s = append(s,7)
s -> +-----+
| ptr |
|*Elem|
+-----+
| len |
|int |
+-----+
| cap |
|int |
+-----+
s = append(s,9)
s -> +-----+
| ptr |
|*Elem|
+-----+
| len |
|int |
+-----+
| cap |
|int |
+-----+
x := append(s,11)
+-------------+-----> +---+---+---+---+
| | [4]int| 5 | 7 | 9 |11 |
| | +---+---+---+---+
s -> +--+--+ x -> +--+--+
[]int | ptr | []int | ptr |
|*int | |*int |
+-----+ +-----+
|len=3| |len=4|
|int | |int |
+-----+ +-----+
|cap=4| |cap=4|
|int | |int |
+-----+ +-----+
y := append(s,12)
+-----> +---+---+---+---+
| [4]int| 5 | 7 | 9 |12 |
| +---+---+---+---+
|
+-------------+-------------+
| | |
s -> +--+--+ x -> +--+--+ y -> +--+--+
[]int | ptr | []int | ptr | []int | ptr |
|*int | |*int | |*int |
+-----+ +-----+ +-----+
|len=3| |len=4| |len=4|
|int | |int | |int |
+-----+ +-----+ +-----+
|cap=4| |cap=4| |cap=4|
|int | |int | |int |
+-----+ +-----+ +-----+
fmt.Println(s,x,y)
}
希望这个可视化解释对你有帮助!
英文:
dystroy explained it very well. I like to add a visual explanation to the behaviour.
A slice is only a descriptor of an array segment. It consists of a pointer to the array (ptr), the length of the segment (len), and capacity (cap).
+-----+
| ptr |
|*Elem|
+-----+
| len |
|int |
+-----+
| cap |
|int |
+-----+
So, the explanation of the code is as follow;
func main() {
+
|
s := []int{5} | s -> +-----+
| []int | ptr +-----> +---+
| |*int | [1]int| 5 |
| +-----+ +---+
| |len=1|
| |int |
| +-----+
| |cap=1|
| |int |
| +-----+
|
s = append(s,7) | s -> +-----+
| []int | ptr +-----> +---+---+
| |*int | [2]int| 5 | 7 |
| +-----+ +---+---+
| |len=2|
| |int |
| +-----+
| |cap=2|
| |int |
| +-----+
|
s = append(s,9) | s -> +-----+
| []int | ptr +-----> +---+---+---+---+
| |*int | [4]int| 5 | 7 | 9 | |
| +-----+ +---+---+---+---+
| |len=3|
| |int |
| +-----+
| |cap=4|
| |int |
| +-----+
|
x := append(s,11) | +-------------+-----> +---+---+---+---+
| | | [4]int| 5 | 7 | 9 |11 |
| | | +---+---+---+---+
| s -> +--+--+ x -> +--+--+
| []int | ptr | []int | ptr |
| |*int | |*int |
| +-----+ +-----+
| |len=3| |len=4|
| |int | |int |
| +-----+ +-----+
| |cap=4| |cap=4|
| |int | |int |
| +-----+ +-----+
|
y := append(s,12) | +-----> +---+---+---+---+
| | [4]int| 5 | 7 | 9 |12 |
| | +---+---+---+---+
| |
| +-------------+-------------+
| | | |
| s -> +--+--+ x -> +--+--+ y -> +--+--+
| []int | ptr | []int | ptr | []int | ptr |
| |*int | |*int | |*int |
| +-----+ +-----+ +-----+
| |len=3| |len=4| |len=4|
| |int | |int | |int |
| +-----+ +-----+ +-----+
| |cap=4| |cap=4| |cap=4|
| |int | |int | |int |
+ +-----+ +-----+ +-----+
fmt.Println(s,x,y)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论