181

假设我有这些类型:

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

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

4

4 回答 4

182

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

这样做的原因是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 已经提出的那样。

于 2013-04-11T15:11:15.713 回答
41

您似乎在要求与此等效的东西:

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相对较小,两个string切片引用:2 * 3 * 8 = 64 位架构机器上的 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"
    }
}
于 2013-04-11T11:07:12.683 回答
34

我会调整您的最后建议并使用仅索引版本的范围。

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

在我看来,在测试行和设置行中n.Attr[i]明确引用似乎更简单,而不是使用一个和另一个。KeyValattrn.Attr[i]

于 2013-04-15T11:24:28.470 回答
15

例如:

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:""}
于 2013-04-11T09:28:17.903 回答