在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]
作为另一个。