Golang:附加切片或没有分配

kir*_*irk -4 append go slice

append()当给定切片的容量不足时,Go的功能仅分配新的切片数据(另请参阅:https://stackoverflow.com/a/28143457/802833).这可能导致意外行为(至少对我来说是一个golang新手):

package main

import (
    "fmt"
)

func main() {

    a1 := make([][]int, 3)
    a2 := make([][]int, 3)
    b := [][]int{{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}
    common1 := make([]int, 0)
    common2 := make([]int, 0, 12) // provide sufficient capacity
    common1 = append(common1, []int{10, 20}...)
    common2 = append(common2, []int{10, 20}...)

    idx := 0
    for _, k := range b {
        a1[idx] = append(common1, k...) // new slice is allocated
        a2[idx] = append(common2, k...) // no allocation
        idx++
    }

    fmt.Println(a1)
    fmt.Println(a2) // surprise!!!
}
Run Code Online (Sandbox Code Playgroud)

输出:

[[10 20 1 1 1] [10 20 2 2 2] [10 20 3 3 3]]

[[10 20 3 3 3] [10 20 3 3 3] [10 20 3 3 3]]

https://play.golang.org/p/8PEqFxAsMt

那么,Go中的(idomatic)方式是强制分配新切片数据还是更精确地确保切片参数append()保持不变?

kos*_*tix 5

您可能会对切片在Go中的工作方式保持错误的认识.

将元素追加到切片时,调用将append()返回一个新切片.如果没有发生重新分配,则两个切片值 - 您调用的切片值append()和返回的切片值 - 共享相同的支持数组,它们将具有不同的长度; 观察:

package main

import "fmt"

func main() {
    a := make([]int, 0, 10)
    b := append(a, 1, 2, 3)
    c := append(a, 4, 3, 2)
    fmt.Printf("a=%#v\nb=%#v\nc=%#v\n", a, b, c)
}
Run Code Online (Sandbox Code Playgroud)

输出:

a=[]int{}
b=[]int{4, 3, 2}
c=[]int{4, 3, 2}
Run Code Online (Sandbox Code Playgroud)

所以len(a) == 0,len(b) == 3,len(c) == 3,和第二次调用append()owerwrote什么第一个做,因为所有切片共享相同的底层数组.

至于后备阵列的重新分配,规范很明确:

如果s的容量不足以容纳附加值,则追加分配一个新的,足够大的底层数组,该数组既适合现有切片元素又适合其他值.否则,追加重新使用底层数组.

由此可见:

  1. append() 如果被提取的切片的容量足够,则永远不要复制底层存储.
  2. 如果没有足够的容量,则将重新分配阵列.

也就是说,给定s要附加N元素的切片,iff将不会重新分配cap(s) - len(s) ? N.

因此,我怀疑你的问题不是关于意外的重新分配结果,而是关于在Go中实现的切片的概念.要吸收的代码理念是append() 返回结果切片值,除非您完全理解其后果,否则您应该在调用后使用该值.

我建议从这开始,以充分理解它们.