

新闻资讯
技术百科range遍历slice时v是副本,修改v不影响原元素;遍历map顺序随机且v也是副本;循环变量被闭包捕获时需局部绑定;channel遍历会阻塞至关闭。
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[i] = ...
[]*int 类型,v 是指针副本,解引用后可间接修改目标值,但不推荐这种写法,易混淆Go 规范明确要求 range 遍历 map 的顺序是**随机的**(从 Go 1.0 起即如此),每次运行结果都可能不同。同时,v 仍是 value 的拷贝。
典型误用场景:依赖遍历顺序做逻辑判断,或试图修改 v 来更新 map 值:
m := map[string]int{"a": 1, "b": 2}
for k, v := range m {
v++ // 不影响 m[k]
fmt.Println(k, v) // 输出顺序不确定
}keys := make([]string, 0, len(m)); for k := range m { keys = append(keys, k) }; sort.Strings(keys); for _, k := range keys { ... }
m[k] = new_value
sync.Map(仅适用于读多写少场景)当在循环内启动 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(顺序不定)
}()
}range,所有 for 循环都存在go func(val int) { ... }(v)
go vet 能发现部分此类问题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,然后退出 }
range,尤其在主 goroutine 中,会导致程序 hang 住
for { select { case v, ok :=
range 的行为高度依赖类型(slice/map/channel),看似统一语法,实则语义差异大。最容易忽略的是:它不提供“引用遍历”,也不保证顺序,更不会自动帮你同步或释放资源。写的时候多想一层“这个 v 到底是谁的副本”。