当前位置: 首页 > 知识库问答 >
问题:

循环中的切片似乎保留了上一个/最后一个引用(取决于切片的长度)

颜文康
2023-03-14

这看起来很奇怪,在一个循环中有一个局部变量,为每个循环分配了新的值,我将该片附加到一个全局sliceWrapper。循环完成后,全局切片内的所有值仅包含对该局部切片变量上最后一个值集的引用。

代码:

package main

import (
    "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内置函数将元素追加到片段的末尾。如果目的地具有足够的容量,则会重新许可该目的地以容纳新元素。

如果上面的条件是造成它的原因,那么在循环内打印的初始切片的值不应该也与切片相同吗?

游乐场-https://play.golang.org/p/b3SDGoA2Lzv

PS:不幸的是,我为代码编写的测试用例只有3层嵌套,而且通过得很好。我现在必须负责循环内切片的复制。

共有2个答案

崔涵亮
2023-03-14

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

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

    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”的较短片段已达到其容量,并附加到该片段后将分配一个新数组,并创建一个具有较大容量的片段。

  • 如果你的程序中有行,初始化切片=append(初始化切片,我),新数组将在循环之前分配。循环内的append(...)s不会导致新的分配。
  • 如果您的程序中没有该行,循环中的append(...)s将导致新的分配,因此每个都有不同的内存位置,这就是为什么它们不会互相覆盖。

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

江建明
2023-03-14
// 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)
    ...
}

打印的地址容量的首字母Slice和片如上。它的输出如下:

Slice Value : 0xc00009c000 - 2
Slice Value : 0xc0000a6000 - 0xc00009c000
Slice Value : 0xc00009e040 - 0xc00009c000

如果对行进行注释,则输出如下:

Slice Value : 0xc00009e000 - 4
Slice Value : 0xc00009e000 - 0xc00009e000
Slice Value : 0xc00009e000 - 0xc00009e000

然后,为什么它输出如你所期望的评论行?
因为在这个场景中,初始切片的容量是2。当追加新元素时,将分配一个新的底层数组,因为它没有足够的空间来实现追加操作。
当你取消注释该行时,初始化切片的容量为4,它将修改该数组

参考:追加单据

append内置函数将元素追加到片段的末尾。如果目的地具有足够的容量,则会重新许可该目的地以容纳新元素。否则,将分配一个新的底层数组。Append返回更新的切片。因此,有必要将append的结果存储在保存切片本身的变量中:slice=append(slice,elem1,elem2)slice=append(slice,anotherSlice…)作为一种特殊情况,向字节片追加字符串是合法的,如下所示:slice=append([]字节(“hello”),“world”…)

 类似资料:
  • 提取切片的最后一个元素的方式是什么? 上述解决方案可行,但似乎有些尴尬。

  • 问题内容: 有没有办法只分割列表中的第一项和最后一项? 例如; 如果这是我的清单: 我 想 这样做(显然是无效的语法): 我尝试过的一些方法: 问题答案: 单程: 更好的方法(不使用切片,但更易于阅读):

  • 问题内容: 我在做下面的运动。 创建一个包含四个元素的切片。 创建一个新切片,并将第三个和第四个元素仅复制到其中。 我已经返回了以下程序 我程序的输出是。但我希望newElements切片为[3 4]- 我的程序出了什么问题。 问题答案: 使用内置的复制功能将元素从一个切片复制到另一个切片。 在操场上跑 您可以使用append创建切片并在单个语句中复制元素,但是代码并不像使用copy那样明显。 在

  • 问题内容: 提取切片的最后一个元素的Go方法是什么? 上面的解决方案有效,但看起来很尴尬。 问题答案: 仅读取切片的最后一个元素: 要删除它: 参见此页面有关切片技巧

  • 问题内容: 我在4Gb机器的64位linux操作系统中运行以下代码: 当我运行它时,我得到: 如果我更改,我将得到: 当我使用片大小的内存时,我原本希望如此,但是当我尝试使用时,我得到: 因此,显然我无法创建大小为的切片,这使我们想到了一个问题:如果内存不是问题,那么我在Go中无法创建的最大切片是什么? 我记得在Java中,原始数组索引是通过type来管理的,因此,原始数组的最大大小是的最大值,如

  • 我想把一个切片的末尾写到同一个切片的顶部。 我见过如何在锈菌阵列的两个可变切片上操作 我希望获得尽可能高的性能(例如,通过使用)。