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

循环中的切片似乎保留了前一个/最后一个引用

循环中的切片似乎保留了前一个/最后一个引用

Go
弑天下 2022-06-13 17:25:44
这看起来很奇怪,在循环中有一个局部变量slice,为每个循环分配了新值,我将该切片附加到全局sliceWrappers. 循环完成后,全局切片内的所有值仅包含对该局部切片变量上设置的最后一个值的引用。代码:package mainimport (    "fmt"    "strconv")func main() {    var sliceWrappers [][]string    initialSlice := append([]string{}, "hi")    initialSlice = append(initialSlice, "there")        // Remove this line and it works fine    initialSlice = append(initialSlice, "I'm")        for i := 0; i < 2; i++ {        slice := append(initialSlice, strconv.Itoa(i))        fmt.Printf("Slice Value : %+v, Initial Value : %+v\n", slice, initialSlice)        sliceWrappers = append(sliceWrappers, slice)    }    for _, sliceWrapper := range sliceWrappers {        fmt.Printf("%+v\n", sliceWrapper)    }}实际输出:Slice Value : [hi there I'm 0], Initial Value : [hi there I'm]Slice Value : [hi there I'm 1], Initial Value : [hi there I'm][hi there I'm 1][hi there I'm 1]预期输出:Slice Value : [hi there I'm 0], Initial Value : [hi there I'm]Slice Value : [hi there I'm 1], Initial Value : [hi there I'm][hi there I'm 0]  <------ This is not happening[hi there I'm 1]如果我删除initialSlice = append(initialSlice, "I'm")行,那么它会完美运行。Slice Value : [hi there 0], Initial Value : [hi there]Slice Value : [hi there 1], Initial Value : [hi there][hi there 0]  <------ Works Perfectly[hi there 1]我相信这与追加有关append 内置函数将元素附加到切片的末尾。如果它有足够的容量,则重新划分目标以容纳新元素。如果上述条件是造成它的原因,那么initialSlice在循环内打印的值不应该也与slice?游乐场- https://play.golang.org/p/b3SDGoA2LzvPS:不幸的是,我为我的代码编写了只有 3 层嵌套的测试用例,它通过了。我现在必须处理循环内切片的复制。
查看完整描述

2 回答

?
慕娘9325324

TA贡献1783条经验 获得超4个赞

// Remove this line and it works fine

//initialSlice = append(initialSlice, "I'm")

fmt.Printf("Slice Value : %p - %d\n", initialSlice, cap(initialSlice))

...

for i := 0; i < 2; i++ {

    slice := append(initialSlice, strconv.Itoa(i))

    fmt.Printf("Slice Value : %p - %p\n", slice, initialSlice)

    ...

}

如上打印initialSlice和slice的addressand 。当取消注释附加时,我是 line。它输出如下:capacity


Slice Value : 0xc00009c000 - 2

Slice Value : 0xc0000a6000 - 0xc00009c000

Slice Value : 0xc00009e040 - 0xc00009c000

如果注释该行,则输出如下:


Slice Value : 0xc00009e000 - 4

Slice Value : 0xc00009e000 - 0xc00009e000

Slice Value : 0xc00009e000 - 0xc00009e000

然后为什么它在评论该行时按预期输出?

因为在这个场景中,initialSlice的容量是2。当追加新元素时将分配一个新的底层数组,因为它没有足够的空间来实现追加操作。

当您取消注释该行时,initialSlice的容量为4,它将就地修改数组。


参考:附加文档


append 内置函数将元素附加到切片的末尾。如果它有足够的容量,则重新划分目标以容纳新元素。如果没有,将分配一个新的底层数组。Append 返回更新后的切片。因此有必要将 append 的结果存储在保存切片本身的变量中: slice = append(slice, elem1, elem2) slice = append(slice, anotherSlice...) 作为一种特殊情况,合法的将字符串附加到字节切片,如下所示: slice = append([]byte("hello "), "world"...)


查看完整回答
反对 回复 2022-06-13
?
开满天机

TA贡献1786条经验 获得超13个赞

切片基于指向数组的指针、长度和容量。您slice在sliceWrappers第一次迭代中放入(因此它包含指向数组的指针)。在第二次迭代中,append(initialSlice, strconv.Itoa(i))调用更改了同一个数组中的值,因为内存位置没有改变。在第一次和第二次迭代中都指向这个数组slice,所以最终的两个切片都sliceWrappers指向相同的数据。


您可以通过在将数据添加到之前将数据复制到新切片来避免这种情况sliceWrappers:


    for i := 0; i < 2; i++ {

        slice := append(initialSlice, strconv.Itoa(i))

        fmt.Printf("Slice Value : %+v, Initial Value : %+v\n", slice, initialSlice)

        copiedSlice := make([]string, len(slice))

        copy(copiedSlice, slice)

        sliceWrappers = append(sliceWrappers, copiedSlice)

    }

这给出了预期的输出:


Slice Value : [hi there I'm 0], Initial Value : [hi there I'm]

Slice Value : [hi there I'm 1], Initial Value : [hi there I'm]

[hi there I'm 0]

[hi there I'm 1]

至于删除线initialSlice = append(initialSlice, "I'm"):当您附加到切片时,它将检查它是否可以在容量内适应新长度。如果没有,它将分配一个新数组(从而分配新的内存位置)。包含“hi there”的较短切片已达到其容量,附加到它将分配一个新数组并创建一个容量更大的切片。


如果您initialSlice = append(initialSlice, "I'm")的程序中有该行,则新数组将在循环之前分配。循环内的append(...)s 不会导致新的分配。

如果您的程序中没有该行,append(...)则循环内的 s 将导致新的分配,因此每个最终都会有不同的内存位置,这就是它们不会相互覆盖的原因。

我的来源是Go Slices:用法和内部结构 https://blog.golang.org/slices-intro#TOC_4。


查看完整回答
反对 回复 2022-06-13
  • 2 回答
  • 0 关注
  • 119 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号