迭代时更改值


153

假设我有以下几种类型:

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

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


2
因此,您需要某种Array.prototype.forEachJavaScript吗?
Florian Margaine 2013年

这是一个有趣的想法,可能是一个解决方案,但是在服务器端语言中,调用一个将在每次迭代中依次调用一个函数的函数看起来很繁琐。缺少泛型会使这种感觉更加沉重。
DenysSéguret13年

老实说,我认为那没那么重。调用一个或两个函数非常便宜,这通常是编译器最优化的。我会尝试一下并对其进行基准测试,以查看它是否符合要求。
Florian Margaine 2013年

由于Go缺少泛型,因此恐怕传递给函数的函数forEach一定会以类型断言开头。确实没有比更好attr := &n.Attr[i]
DenysSéguret13年

Answers:


152

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

原因是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所建议的那样。


16
考虑这一点的一种方法是分配一个值会导致一个副本。如果看到val:= x [1],那么val是x [1]的副本将完全不奇怪。请记住,范围的每次迭代都是从分配索引和值变量开始的,而不是将范围视为做特殊的事情,而是分配而不是引起复制的范围。
安迪·戴维斯

抱歉,我在这里还是有点困惑。如果for循环的第二个值是a [i],那么a[i]for循环和a[i]我们编写的之间有什么区别?看起来好像一样,但是不是吗?
田阮潢

1
@TiếnNguyễnHoàng range返回a[i]为第二个返回值。val = a[i]通过完成此操作,range创建了该值的副本,因此对的任何写操作都将val应用于副本。
nemo

37

您似乎在要求以下内容:

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

2
这是一个遗憾,value := &someMap[key]会如果没有工作someMapmap
warvariuc

peterSO在您的第一个代码段中,您是否不必遵从attr来为其分配内容?即*attr.Val = "something"
Homam Bahrani

25

我会采纳您的最后建议,并使用范围的仅索引版本。

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

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


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:""}

我以为这很明显,但我不想更改获得的结构(它们是从go.net/html包装中取出的)
DenysSéguret13年

1
@dystroy:上面的第二种方法不会更改OP的类型(“结构”)。
zzzz

是的,我知道,但实际上并没有带来什么。我期待一个可能会错过的想法。我相信没有比这更简单的解决方案了。
DenysSéguret13年

1
@dystroy:它确实带来了一些东西,没有在这里复制并返回整个Attribute。是的,我有信心采用slice元素的地址来避免对该元素进行双拷贝(r + w)更新是最佳解决方案。
zzzz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.