1 回答
TA贡献1836条经验 获得超5个赞
问题
让我们引用语言规范在循环上所说的内容for ... range:
带有“range”子句的“for”语句遍历数组、切片、字符串或映射的所有条目,或通道上接收的值。对于每个条目,如果存在,它将迭代值分配给相应的迭代变量,然后执行该块。
所以,在
for _, item := range dbUpdate { ... }
整个语句形成了一个作用域,其中声明了一个名为的变量item,并为它分配了每个元素的值dbUpdate,依次从第一个到最后一个 - 因为语句执行它的迭代。
Go 中的所有赋值,无论何时何地,都会将被赋值的表达式的值复制到接收该值的变量中。
所以,当你有
type subDB struct {
Name string `json:"name"`
Interests []string `json:"interests"`
}
var dbUpdate []subDB
你有一个切片,它的后备数组包含一组元素,每个元素都有 type subDB。
因此,当for ... range迭代切片时,每次迭代subDB都会完成当前切片元素中包含的值的字段的浅拷贝:这些字段的值被复制到变量item中。
我们可以将发生的事情重写为:
for i := 0; i < len(dbUpdate); i++ {
var item subDB
item = dbUpdate[i]
...
}
如您所见,如果您item在循环体中发生变异,您对其所做的更改不会以任何方式影响当前正在迭代的集合元素。
解决方案
从广义上讲,解决方案是充分了解 Go 在其实现的大部分内容中都非常简单的事实,因此range并不神奇:迭代变量只是一个变量,对它的赋值只是一个赋值。
至于解决特定问题,有多种方法。
通过索引引用集合元素
做
for i := range dbUpdate {
dbUpdate[i].FieldName = value
}
对此的推论是,有时,当元素很复杂或者您想将其突变委托给某个函数时,您可以使用指向它的指针:
for i := range dbUpdate {
p := &dbUpdate[i]
mutateSubDB(p)
}
...
func mutateSubDB(p *subDB) {
p.SomeField = someValue
}
将指针保留在切片中
如果你的切片像
var dbUpdates []*subDB
...并且您会保留指向(通常是堆分配的)SubDB值的指针,
for _, ptr := range dbUpdate { ... }
语句自然会将指向 SubDB(匿名)变量的指针复制到其中ptr,因为切片包含指针,因此赋值复制了一个指针。
由于所有包含相同地址的指针都指向相同的值,因此通过保存在迭代变量中的指针改变目标变量将改变切片元素所指向的相同事物。
选择哪种方法通常应该取决于考虑因素,而不是考虑如何迭代元素——因为一旦你理解了你的代码为什么不起作用,你就不再有这个问题了。
通常:如果您的值非常大,请考虑保留指向它们的指针。如果您的值需要同时从多个位置引用,请保留指向它们的指针。在其他情况下,直接保留这些值——这极大地提高了 CPU 数据缓存的局部性(简单地说,当您即将访问下一个元素时,它的内容很可能已经从内存中获取,当CPU 必须追逐一个指针以通过它访问某个任意内存位置)。
- 1 回答
- 0 关注
- 153 浏览
添加回答
举报