Go中range遍历切片、数组、map时,v均为值拷贝,修改v不影响原数据;需通过索引(如nums[i])或键(如m[k])修改;循环变量v复用易致goroutine或取址错误。

range 遍历切片时,v 是副本,修改 v 不影响原切片元素
Go 中 range 遍历切片([]T)时,第二个变量 v 是每个元素的**值拷贝**,不是引用。直接修改 v 不会改变底层数组内容。
常见错误写法:
nums := []int{1, 2, 3}
for _, v := range nums {
v *= 2 // 这里改的是 v 的副本,nums 不变
}
// nums 仍是 [1, 2, 3]
正确做法是用索引访问并修改:
- 需要修改原切片 → 用
i索引:nums[i] *= 2 - 只读遍历 →
v更安全、更清晰 - 若切片元素是结构体指针(
[]*T),v是指针副本,仍可间接修改字段(但别误以为是结构体本身被复制)
range 遍历 map 时,遍历顺序不保证,且 v 同样是值拷贝
Go 的 map 是哈希表实现,range 遍历顺序**随机且每次不同**(从 Go 1.0 起故意设计,防依赖顺序的 bug)。同时,v 是每个 value 的副本。
立即学习“go语言免费学习笔记(深入)”;
示例:
m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
fmt.Println(k, v) // 每次运行输出顺序可能不同
}
如果需要固定顺序(比如按 key 排序输出):
- 先收集所有 key 到切片:
keys := make([]string, 0, len(m)) - 用
sort.Strings(keys) - 再遍历
keys,通过m[k]取值
注意:v 是副本,修改 v 不影响 m[k];要更新 map 值,必须写 m[k] = newV。
range 遍历数组时,len 和 cap 固定,但 range 仍按元素值拷贝
数组(如 [3]int)是值类型,range 遍历时同样把每个元素以值方式赋给 v。虽然数组长度不可变,但遍历行为和切片一致。
关键区别在于:传参或赋值时,数组会整体拷贝;而切片只拷贝 header(指针+长度+容量)。
- 遍历
[5]int时,v是int类型,不是引用 - 想修改原数组 → 必须用索引:
a[i] = ... - 用
range遍历数组比切片稍快(无 header 解引用开销),但差异通常可忽略
for-range 中的变量复用陷阱:循环变量地址相同
这是最隐蔽也最常踩的坑:在循环中启动 goroutine 或保存变量地址时,所有迭代共享同一个 v 变量内存位置。
错误示例(闭包捕获的是同一个 v):
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v) // 全部打印 "c"(最后一次赋值)
}()
}
// 正确做法:显式传参
for _, v := range values {
go func(val string) {
fmt.Println(val)
}(v)
}
同理,往切片中 append 取地址也会出错:
- ❌
ptrs = append(ptrs, &v)→ 所有指针指向同一内存 - ✅
ptrs = append(ptrs, &values[i])或先拷贝:val := v; ptrs = append(ptrs, &val)
这个陷阱和类型无关(切片/map/数组都存在),本质是 Go 循环变量复用机制决定的 —— 它不是每次迭代新建变量。










