有人可以解释一下在 Golang 中向切片追加元素的奇怪行为吗?

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

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

切片只是数组的一部分窗口,它没有具体的存储。

这意味着如果你有两个切片指向同一个数组的同一部分,这两个切片必须“包含”相同的值。

具体的情况如下:

  1. 当你进行第一次append操作时,你得到一个大小为2的新切片,它指向一个大小为2的底层数组。
  2. 当你进行下一次append操作时,你得到一个大小为3的新切片,但底层数组的大小为4(append通常会分配比实际需要的更多的空间,以避免每次append都需要重新分配内存)。
  3. 这意味着下一次的append操作不需要新的数组。所以xy都将使用与前一个切片s相同的底层数组。你先在这个数组的一个位置写入11,然后写入12,即使你得到了两个不同的切片(记住,它们只是窗口)。

你可以通过在每次append操作后打印切片的容量来验证这一点:

fmt.Println(cap(s))

如果你想在xy中有不同的值,你可以进行复制,例如:

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 :

  1. When you do the first append, you get a new slice of size 2 over an underlying array of size 2.
  2. When you do the next append, you get a new slice of size 3 but the underlying array is of size 4 (append usually allocates more space than the immediately needed one so that it doesn't need to allocate at every append).
  3. This means the next append doesn't need a new array. So x and y both will use the same underlying array as the precedent slice s. You write 11 and then 12 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)                                                    
} 

huangapple
  • 本文由 发表于 2014年12月19日 22:38:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/27568213.html
匿名

发表评论

匿名网友

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

确定