假设我有这些类型:
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
获取指针?
显然我不想仅仅为了迭代而改变结构,更冗长的解决方案不是解决方案。
Array.prototype.forEach
吗?
forEach
的函数必然以类型断言开头。这并不比 attr := &n.Attr[i]
好。
不,你想要的缩写是不可能的。
这样做的原因是 range
从您正在迭代的切片中复制值。 specification about range 说:
范围表达式 第一个值 第二个值(如果存在第二个变量) 数组或切片 a [n]E、*[n]E 或 []E 索引 i int a[i] E
因此,range 使用 a[i]
作为数组/切片的第二个值,这实际上意味着该值被复制,使原始值不可触及。
following code 演示了此行为:
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{
{"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"
}
}
someMap
是 map
,value := &someMap[key]
将不起作用
*attr.Val = "something"
我会调整您的最后建议并使用仅索引版本的范围。
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]
表示另一个。
例如:
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
包)
a[i]
和我们写的a[i]
有什么区别?看起来是一样的,但实际上不是,对吧?range
返回a[i]
作为其第二个返回值。由range
完成的此操作val = a[i]
创建了值的副本,因此对val
的任何写入操作都将应用于副本。