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]
正确做法是用索引访问并修改:
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 排序输出):
注意: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 循环变量复用机制决定的 —— 它不是每次迭代新建变量。