迭代时更改值

假设我有这些类型:

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获得指针?

显然,我不想仅仅为了迭代而更改结构,更冗长的解决方案不是解决方案。

104216 次浏览

例如:

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}]}

这样可以避免创建 Attribute类型值的副本(可能很大) ,代价是牺牲切片边界检查。在您的示例中,类型 Attribute相对较小,在一台64位元机器上有两个 string片引用: 2 * 3 * 8 = 48字节。

你也可以简单地写:

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

但是,使用 range子句获得等效结果的方法是:

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

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

这样做的原因是,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

因此,range 使用 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 已经提出的那样。

我将采用您上一个建议,并使用仅索引的 range 版本。

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

对我来说,在测试 Key的行和设置 Val的行中都明确地引用 n.Attr[i],而不是一个行使用 attr,另一个行使用 n.Attr[i],这样做似乎更简单。