我有这个简单的代码:
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}
。这里发生了什么?
答案 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)
}