为了账号安全,请及时绑定邮箱和手机立即绑定

意外的切片追加行为

意外的切片追加行为

Go
偶然的你 2021-12-07 19:36:14
我今天在 go 代码中遇到了奇怪的行为:当我附加elements到slicein 循环然后尝试slices根据循环的结果创建新的时,最后一次append覆盖slices来自 previous appends。在这个特定的例子中,这意味着sliceFromLoop j,g和hslice 的最后一个元素分别不是100,101和102,但是......总是102!第二个例子 -sliceFromLiteral表现如预期。package mainimport "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)}play.golang 链接:https : //play.golang.org/p/INADVS3Ats经过一些阅读、挖掘和试验,我发现这个问题源于slices引用相同的底层array 值,可以通过slice在追加任何内容之前复制到新值来解决,但它看起来很......犹豫不决。基于旧切片创建许多新切片而不用担心更改旧切片的值的惯用方法是什么?
查看完整描述

3 回答

?
largeQ

TA贡献2039条经验 获得超7个赞

不要分配append给除自身以外的任何东西。


正如您在问题中提到的,混淆是由于append既更改了底层数组又返回了一个新切片(因为长度可能会更改)。你会想象它复制了那个后备数组,但它没有,它只是分配一个slice指向它的新对象。由于i从不改变,所有这些附加值都会不断将 的值更改backingArray[12]为不同的数字。


将此与appending 与数组进行对比,后者每次分配一个新的文字数组。


所以是的,您需要先复制切片,然后才能对其进行处理。


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


查看完整回答
反对 回复 2021-12-07
?
慕哥9229398

TA贡献1877条经验 获得超6个赞

如果您需要切片的副本,除了复制切片之外别无他法。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


查看完整回答
反对 回复 2021-12-07
?
RISEBY

TA贡献1856条经验 获得超5个赞

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


func copyAndAppend(source []string, items ...string) []string {

    l := len(source)

    return append(source[:l:l], items...)

}

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


查看完整回答
反对 回复 2021-12-07
  • 3 回答
  • 0 关注
  • 167 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信