奇怪的 golang "append" 行为(覆盖切片中的值)

Strange golang "append" behavior (overwriting values in 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}。这里发生了什么?

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

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 上的工作代码。

这是因为在 for 循环中,您使用的是 copy 而不是 slice/array 元素本身。

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

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

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

输出(在 Go Playground 上尝试):

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

查看可能的重复项

此行为的原因

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

摘自

Pointers are values just like let's say int numbers. The difference is the interpretation of that value: pointers are interpreted as memory addresses, and ints are interpreted as integer numbers.

When you want to change the value of a variable of type int, you pass a pointer to that int which is of type *int, and you modify the pointed object: *i = newvalue (the value assigned is an int).

Same goes with pointers: when you want to change the value of a variable of pointer type *int, you pass a pointer to that *int which is of type **int and you modify the pointed object: *i = &newvalue (the value assigned is an *int).

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

在循环的每次迭代中,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