问题历史:
for-range 结构在 Go 中出现,作为迭代集合(切片、数组、map、字符串)的一种方式。Go 开发人员引入了一种优化:在每次循环迭代时,进行值的复制,而不是直接通过引用使用,这可能会导致不明显的错误,特别是在涉及循环变量时。
问题:
很多人错误地尝试在 range 内获取变量的地址(例如,&v),认为得到的是集合元素的地址,但实际上得到的是本地变量的地址。
解决方案:
在 for-range 循环中的每次迭代都会创建新的迭代器变量(key, value)的副本。对于简单类型,这没有问题,但对于结构体在保存元素的指针时会导致意外情况——它总是指向同一个变量,而不是切片中的不同元素。
示例代码:
people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // 所有 ptrs 都将引用同一个 p }
关键特性:
在 range 内保存对 value 变量的引用会发生什么?
所有引用都会指向同一块内存,因为 value 是临时变量。
for _, v := range someSlice { ptrs = append(ptrs, &v) } // 所有 ptrs 都包含对同一变量的引用!
能否通过 range 中的 value 通过引用修改集合元素?
不能,修改 value 不会影响集合中的原始元素。要修改,必须通过索引访问。
for _, v := range arr { v.Field = 10 // arr 不会改变 } for i := range arr { arr[i].Field = 10 // 正确 }
for-range 对 map 是否保证遍历顺序?
不,Go 中对 map 的迭代顺序是不确定的,每次应用程序运行时可能会不同。
开发人员尝试通过 range 对结构体元素的引用列表进行序列化,并将 &value 保存到单独的切片中。结果是切片包含相同的地址。
优点:
缺点:
通过索引迭代并保存指向所需数组元素的指针:
for i := range arr { ptrs = append(ptrs, &arr[i]) }
优点:
缺点: