range遍历slice时v是副本,修改v不影响原元素;遍历map顺序随机且v也是副本;循环变量被闭包捕获时需局部绑定;channel遍历会阻塞至关闭。

range 遍历 slice 时,v 是副本,修改 v 不影响原元素
Go 中 range 遍历 slice 默认返回索引和值的**拷贝**。这意味着直接修改循环变量 v 不会改变底层数组或 slice 元素。
常见错误写法:
nums := []int{1, 2, 3}
for _, v := range nums {
v *= 2 // 这里修改的是 v 的副本,nums 不变
}
// nums 仍是 [1, 2, 3]正确做法是通过索引访问并赋值:
nums := []int{1, 2, 3}
for i := range nums {
nums[i] *= 2 // 修改原 slice 元素
}
// nums 变为 [2, 4, 6]- 如果只需要索引,用
for i := range slice更简洁、高效 - 如果需要索引和值,且要修改原 slice,必须用
slice[i] = ... - 注意:对
[]*int类型,v是指针副本,解引用后可间接修改目标值,但不推荐这种写法,易混淆
range 遍历 map 时,遍历顺序不保证,且 v 同样是副本
Go 规范明确要求 range 遍历 map 的顺序是**随机的**(从 Go 1.0 起即如此),每次运行结果都可能不同。同时,v 仍是 value 的拷贝。
立即学习“go语言免费学习笔记(深入)”;
典型误用场景:依赖遍历顺序做逻辑判断,或试图修改 v 来更新 map 值:
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
v++ // 不影响 m[k]
fmt.Println(k, v) // 输出顺序不确定
}- 要按 key 排序遍历,需先提取 keys 到 slice,排序后再遍历:
keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys); for _, k := range keys { ... } - 要更新 map 中的值,必须显式写
m[k] = new_value - 遍历时并发读写 map 会 panic,需加锁或使用
sync.Map(仅适用于读多写少场景)
range 在 for 循环中复用变量导致闭包陷阱
当在循环内启动 goroutine 或构造闭包,并捕获循环变量时,所有闭包共享同一个变量地址,最终看到的是最后一次迭代的值。
ints := []int{1, 2, 3}
for _, v := range ints {
go func() {
fmt.Println(v) // 所有 goroutine 都打印 3
}()
}
time.Sleep(time.Millisecond)修复方式:在循环体内用局部变量绑定当前值:
for _, v := range ints {
v := v // 创建新变量,遮蔽外层 v
go func() {
fmt.Println(v) // 正确输出 1, 2, 3(顺序不定)
}()
}- 这是 Go 中经典的“循环变量逃逸”问题,不仅限于
range,所有 for 循环都存在 - 也可改用索引方式传参:
go func(val int) { ... }(v) - 静态检查工具如
go vet能发现部分此类问题
range 遍历 channel 会阻塞,直到 close 或被 break
range 用于 channel 时,会持续接收直到 channel 关闭。若未关闭且无发送者,循环将永久阻塞。
ch := make(chan int, 2) ch <- 1 ch <- 2 close(ch) // 必须 close,否则 range 永不结束for v := range ch { fmt.Println(v) // 输出 1, 2,然后退出 }
- 不要在未关闭的 channel 上直接
range,尤其在主 goroutine 中,会导致程序 hang 住 - 若需带超时或条件退出,应改用
for { select { case v, ok := - range channel 无法获取索引,只提供接收到的值
range 的行为高度依赖类型(slice/map/channel),看似统一语法,实则语义差异大。最容易忽略的是:它不提供“引用遍历”,也不保证顺序,更不会自动帮你同步或释放资源。写的时候多想一层“这个 v 到底是谁的副本”。










