编程Go 后端工程师

解释一下 Go 中 range 机制在 map 和 slice 上的工作原理。循环变量的使用有什么细微之处,可能会遇到什么错误?

用 Hintsage AI 助手通过面试

答案

循环 for ... range 允许方便地遍历切片(slice)、映射(map)、数组或字符串的元素。

  • 对于 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) }

关键细节: 变量 ivk 等在所有迭代中被重用!这在循环内部通过引用传递或者在 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 字符串的处理不正确,字符切割不正确。