繁体   English   中英

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

[英]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} 这里发生了什么?

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

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

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

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

输出(在Go Playground上试试):

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

查看可能重复的已分配指针字段变为 <nil>

对这种行为的推理

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

摘自这个答案

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

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

指针也一样:当你想改变一个指针类型*int的变量的值时,你将一个指针传递给*int类型的**int并修改指向的对象: *i = &newvalue (值分配的是*int )。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM