在golang中迭代时更改值

假设我有这些types:

type Attribute struct { Key, Val string } type Node struct { Attr []Attribute } 

而且我想迭代我的节点的属性来改变它们。

我本来希望能够做到

 for _, attr := range n.Attr { if attr.Key == "href" { attr.Val = "something" } } 

attr不是一个指针,这是行不通的,我必须这样做

 for i, attr := range n.Attr { if attr.Key == "href" { n.Attr[i].Val = "something" } } 

有更简单还是更快的方法? 是否有可能直接从range指针?

显然,我不想仅仅为了迭代而改变结构,更详细的解决scheme是无法解决的

不,你想要的缩写是不可能的。

原因是range复制了您正在迭代的切片的值。 关于范围的规范说:

 Range expression 1st value 2nd value (if 2nd variable is present) array or slice a [n]E, *[n]E, or []E index i int a[i] E 

因此,范围使用a[i]作为数组/切片的第二个值,这实际上意味着该值被复制,使原始值不可触摸。

以下代码演示了此行为:

 x := make([]int, 3) x[0], x[1], x[2] = 1, 2, 3 for i, val := range x { println(&x[i], "vs.", &val) } 

代码打印完全不同的内存位置,范围内的值和切片中的实际值:

 0xf84000f010 vs. 0x7f095ed0bf68 0xf84000f014 vs. 0x7f095ed0bf68 0xf84000f018 vs. 0x7f095ed0bf68 

所以你唯一能做的就是使用指针或者索引,就像jnml和peterSO已经提出的那样。

例如:

 package main import "fmt" type Attribute struct { Key, Val string } type Node struct { Attr []*Attribute } func main() { n := Node{[]*Attribute{ &Attribute{"foo", ""}, &Attribute{"href", ""}, &Attribute{"bar", ""}, }} for _, attr := range n.Attr { if attr.Key == "href" { attr.Val = "something" } } for _, v := range n.Attr { fmt.Printf("%#v\n", *v) } } 

操场


产量

 main.Attribute{Key:"foo", Val:""} main.Attribute{Key:"href", Val:"something"} main.Attribute{Key:"bar", Val:""} 

替代方法:

 package main import "fmt" type Attribute struct { Key, Val string } type Node struct { Attr []Attribute } func main() { n := Node{[]Attribute{ {"foo", ""}, {"href", ""}, {"bar", ""}, }} for i := range n.Attr { attr := &n.Attr[i] if attr.Key == "href" { attr.Val = "something" } } for _, v := range n.Attr { fmt.Printf("%#v\n", v) } } 

操场


输出:

 main.Attribute{Key:"foo", Val:""} main.Attribute{Key:"href", Val:"something"} main.Attribute{Key:"bar", Val:""} 

你似乎在要求这样的东西:

 package main import "fmt" type Attribute struct { Key, Val string } type Node struct { Attr []Attribute } func main() { n := Node{ []Attribute{ {"key", "value"}, {"href", "http://www.google.com"}, }, } fmt.Println(n) for i := 0; i < len(n.Attr); i++ { attr := &n.Attr[i] if attr.Key == "href" { attr.Val = "something" } } fmt.Println(n) } 

输出:

 {[{key value} {href http://www.google.com}]} {[{key value} {href something}]} 

这样可以避免创buildtypesAttribute值的可能较大的副本,但要牺牲片边界检查。 在你的例子中,typesAttribute是比较小的,两个string切片引用:64位体系结构机器上的2 * 3 * 8 = 48字节。

你也可以简单地写:

 for i := 0; i < len(n.Attr); i++ { if n.Attr[i].Key == "href" { n.Attr[i].Val = "something" } } 

但是,用range子句获得等效结果的方法是创build一个副本,但最小化分片边界检查:

 for i, attr := range n.Attr { if attr.Key == "href" { n.Attr[i].Val = "something" } } 

我会适应你的最后一个build议,并使用索引版本的范围。

 for i := range n.Attr { if n.Attr[i].Key == "href" { n.Attr[i].Val = "something" } } 

对于我来说,在testingKey的行和设置Val的行中都明确提到n.Attr[i]似乎比较简单,而不是使用attr作为一个,而使用n.Attr[i]作为另一个。