意外切片附加行为

时间:2016-02-08 17:48:29

标签: go

我今天在go代码中遇到了奇怪的行为:当我在循环中将elements追加到slice然后尝试根据循环的结果创建新的slices时,最后{{ 1}}覆盖之前append的{​​{1}}。

在此特定示例中,这意味着slices appendssliceFromLoopj切片的最后一个元素不是g,{{1分别是{}和h,但是......总是100

第二个示例 - 101按预期行事。

102

链接到play.golang: https://play.golang.org/p/INADVS3Ats

经过一些阅读,挖掘和试验后,我发现此问题源于102引用相同的sliceFromLiteralpackage main import "fmt" func create(iterations int) []int { a := make([]int, 0) for i := 0; i < iterations; i++ { a = append(a, i) } return a } func main() { sliceFromLoop() sliceFromLiteral() } func sliceFromLoop() { fmt.Printf("** NOT working as expected: **\n\n") i := create(11) fmt.Println("initial slice: ", i) j := append(i, 100) g := append(i, 101) h := append(i, 102) fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) } func sliceFromLiteral() { fmt.Printf("\n\n** working as expected: **\n") i := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println("initial slice: ", i) j := append(i, 100) g := append(i, 101) h := append(i, 102) fmt.Printf("i: %v\nj: %v\ng: %v\nh:%v\n", i, j, g, h) } 值,并且可以通过在附加任何内容之前将slices复制到新的问题来解决但是看起来很......犹豫不决。

基于旧片段创建许多新片段并且不担心更改旧片段值的自觉方式是什么?

3 个答案:

答案 0 :(得分:11)

不要将append分配给除自身以外的任何其他内容。

正如你在问题中提到的,混淆是由于append都改变了底层数组并返回一个新切片(因为长度可能会改变)。你可以想象它会复制那个支持数组,但它并没有,它只是分配一个指向它的新slice对象。由于i永远不会更改,因此所有这些附加内容会将backingArray[12]的值更改为其他数字。

将此对比与append数组进行对比,该数组每次都会分配一个新的文字数组。

所以是的,你需要复制切片才能开始工作。

func makeFromSlice(sl []int) []int {
    result := make([]int, len(sl))
    copy(result, sl)
    return result
}

func main() {
    i := make([]int, 0)
    for ii:=0; ii<11; ii++ {
        i = append(i, ii)
    }
    j := append(makeFromSlice(i), 100)  // works fine
}

解释了切片文字行为,因为如果追加量超过支持数组的cap,则会分配新的数组 。这与切片文字无关,而且与超出限额的内部结构有关。

a := []int{1,2,3,4,5,6,7}
fmt.Printf("len(a) %d, cap(a) %d\n", a, len(a), cap(a))
// len(a) 7, cap(a) 7

b := make([]int, 0)
for i:=1; i<8, i++ {
    b = append(b, i)
}  // b := []int{1,2,3,4,5,6,7}
// len(b) 7, cap(b) 8

b = append(b, 1)  // any number, just so it hits cap

i := append(b, 100)
j := append(b, 101)
k := append(b, 102)  // these work as expected now

答案 1 :(得分:3)

如果您需要切片的副本,除了复制切片之外别无他法。您几乎不应该将append的结果分配给除append的第一个参数之外的变量。它导致很难找到错误,并且根据切片是否具有所需容量而表现不同。

这不是一个常用的模式,但与所有这些性质的东西一样,如果你需要多次重复几行代码,那么你可以使用一个小辅助函数:

func copyAndAppend(i []int, vals ...int) []int {
    j := make([]int, len(i), len(i)+len(vals))
    copy(j, i)
    return append(j, vals...)
}

https://play.golang.org/p/J99_xEbaWo

答案 2 :(得分:0)

实现copyAndAppend函数还有一些更简单的方法:

func copyAndAppend(source []string, items ...string) []string {
    l := len(source)
    return append(source[:l:l], items...)
}

这里我们只确保源没有可用容量,因此强制复制。