奇怪的golang“追加”行为(覆盖切片中的值)

Posted

技术标签:

【中文标题】奇怪的golang“追加”行为(覆盖切片中的值)【英文标题】:Strange golang "append" behavior (overwriting values in slice) 【发布时间】:2016-12-06 04:31:38 【问题描述】:

我有这个简单的代码:

import "fmt"

type Foo struct 
    val int


func main() 
    var a = make([]*Foo, 1)
    a[0] = &Foo0

    var b = [3]FooFoo1, Foo2, Foo3
    for _, e := range b 
        a = append(a, &e)
    

    for _, e := range a 
        fmt.Printf("%v ", *e)
    

我期待它打印0 1 2 3,但它打印0 3 3 3。这里发生了什么?

【问题讨论】:

play.golang.org/p/nftYWXrflD 的游乐场 【参考方案1】:

这是因为在 for 循环中,您使用的是 copy 而不是切片/数组元素本身。

for ... range 会复制它循环的元素,并附加这个临时循环变量的地址 - 在所有迭代中都是相同的。因此,您将相同的指针添加了 3 次。而这个临时变量将在最后一次迭代(数组的最后一个元素)中设置为Foo3,这就是为什么你会看到它打印了 3 次。

修复:不添加循环变量的地址,而是数组元素的地址:

for i := range b 
    a = append(a, &b[i])

输出(在Go Playground 上试试):

0 1 2 3 

查看可能重复的Assigned pointer field becomes <nil>。

这种行为的原因

在 Go 中有 pointer 类型和 non-pointer 类型,但没有 "references"(意思是它用于 C++ 和 Java)。鉴于 Go 中没有“引用”类型,这并不是一个意外的行为。循环变量只是一个“普通”的局部变量,它只能保存一个值(可以是指针也可以是非指针),不能保存引用。

摘自this answer:

指针是值,就像int 数字一样。不同之处在于对该值的解释:指针被解释为内存地址,ints 被解释为整数。

当你想改变一个int类型的变量的值时,你传递一个指向int的指针,它是*int类型的,然后你修改指向的对象:*i = newvalue(分配的值是int)。

指针也是如此:当你想改变一个指针类型为*int的变量的值时,你将一个指针传递给*int,它的类型是**int,然后你修改指向的对象:@987654340 @(分配的值为*int)。

总而言之,循环变量只是一个普通变量,具有您正在循环的数组/切片的元素类型,并且要使其具有实际迭代的值,必须将值分配给它复制值。它在下一次迭代中被覆盖。

【讨论】:

感谢您的解释!但是,这种行为非常令人困惑。不知道他们为什么要这样设计...... 推理是有道理的,对于新手来说肯定会感到困惑。我花了很长时间试图找出问题所在,我认为这可能是一种竞争条件,我什至花了一段时间才找到这个答案,因为我不确定自己在寻找什么。【参考方案2】:

因为您将切片用作指针引用,所以实际上您不是在切片中创建新条目,而是在每次最后一个条目时更新。将代码从指针引用更改为正常值引用它将起作用。

package main

import "fmt"

type Foo struct 
    val int


func main() 
    var a = make([]Foo, 1)
    a[0] = Foo0

    var b = [3]FooFoo1, Foo2, Foo3
    for _, e := range b 
        a = append(a, e)
    

    for i, e := range a 
        fmt.Printf("%d: %v\n", i, e)
    

Go playground 上的工作代码。

【讨论】:

【参考方案3】:

在循环的每次迭代中,e 的值都会发生变化,但每次您将指向 e 的指针传递到切片时。所以你最终会得到一个包含 3 个指向相同值的指针的切片。

您还可以复制该值并传递其指针。由于在每次迭代中,您都在制作值的新副本,因此传递给切片的指针将指向不同的值:

var a = make([]*Foo, 1)
a[0] = &Foo0

var b = [3]FooFoo1, Foo2, Foo3
for _, e := range b 
    eCopy := e
    a = append(a, &eCopy)

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

【讨论】:

以上是关于奇怪的golang“追加”行为(覆盖切片中的值)的主要内容,如果未能解决你的问题,请参考以下文章

golang 自定义切片追加方法

golang 将两个切片追加在一起

golang数组与切片

Golang 从切片追加函数“已评估但未使用”中删除 dup ints

Golang多维切片拷贝

GoLang中的切片扩容机制