奇怪的golang“追加”行为

时间:2016-08-01 07:17:44

标签: arrays for-loop go append slice

我有这个简单的代码:

import "fmt"

type Foo struct {
    val int
}

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

    var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
    for _, e := range b {
        a = append(a, &e)
    }

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

我希望它能打印{0} {1} {2} {3},但会打印{0} {3} {3} {3}。这里发生了什么?

3 个答案:

答案 0 :(得分:11)

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

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

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

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

输出(在Go Playground上尝试):

{0} {1} {2} {3} 

请参阅可能的重复Assigned pointer field becomes <nil>

此行为的推理

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

摘自this answer

  

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

     

如果要更改int类型变量的值,则将指针传递给类型为int的{​​{1}},并修改指向的对象:{ {1}}(分配的值为*int)。

     

与指针相同:当您想要更改指针类型*i = newvalue的变量的值时,您将指针传递给类型为int的{​​{1}}的指针并进行修改指向的对象:*int(指定的值为*int)。

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

答案 1 :(得分:1)

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

package main

import "fmt"

type Foo struct {
    val int
}

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

    var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
    for _, e := range b {
        a = append(a, e)
    }

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

Go playground上的工作代码。

答案 2 :(得分:0)

在循环的每次迭代中,e的值都会改变,但是每次您将指向e的指针传递到切片中时,其值都会改变。因此,您最终得到一个包含3个指向相同值的指针的切片。

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

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

var b = [3]Foo{Foo{1}, Foo{2}, Foo{3}}
for _, e := range b {
    eCopy := e
    a = append(a, &eCopy)
}

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