循环 for ... range 允许方便地遍历切片(slice)、映射(map)、数组或字符串的元素。
示例:
s := []int{1, 2, 3} for i, v := range s { fmt.Println(i, v) } m := map[string]int{"a":1, "b":2} for k, v := range m { fmt.Println(k, v) }
关键细节:
变量 i、v、k 等在所有迭代中被重用!这在循环内部通过引用传递或者在 range 内启动 goroutine 时,常常会导致错误。
如果在遍历 slice 的 range 中启动 goroutine,捕获迭代变量会发生什么?如何避免错误?
答案: 会出现典型错误:在 goroutine 内使用循环变量,它们在循环结束后将会有最后的值。要避免这种情况,需创建局部副本:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
故事
在一个项目中,使用 range 通过多个 goroutine 填充 slice,忘记了创建循环变量的副本。所有 goroutine 打印了相同的值——数组中的最后一个值,这严重破坏了业务逻辑。
故事
在对 map 使用 range 时,将值的引用保存到新的指针切片中。结果,新的切片的所有元素都引用了同一个变量——在循环中使用的那个(值的副本)。在循环外更新这些变量时,出现了 bug(panic:无效的内存地址或意外数据)。
故事
在内部工具中,对 string 使用 range 启动处理重大子字符串的处理程序,但对于每次迭代,我得到了字节偏移量,而不是 Unicode 字符。结果:对 Unicode 字符串的处理不正确,字符切割不正确。