奇怪的golang"追加"行为

Cha*_*Sun 10 arrays for-loop append go 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

icz*_*cza 12

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

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

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

for i := range b {
    a = append(a, &b[i])
}
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

{0} {1} {2} {3} 
Run Code Online (Sandbox Code Playgroud)

请参阅可能重复的已分配指针字段变为<nil>.

推理这种行为

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

摘自这个答案:

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

当要改变类型的变量的值int,则通过一个指向int它的类型的*int,并且修改尖锐的物体:*i = newvalue(分配值是一个int).

也是一样的指针:当要改变指针类型的变量的值*int,则通过一个指向*int它的类型的**int和修改尖锐的物体:*i = &newvalue(分配值是一个*int).

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

  • 感谢您的解释!但这种行为非常令人困惑。不知道为什么他们这样设计...... (2认同)