结论:range循环内部拿到的永远是数组元素的副本。无论range a还是range a,均先得到数组值再遍历;a需先解引用,但拷贝开销相同;改v无效,须用索引或解引用赋值。

for-range 拿到的是 *a 还是 a 的副本?
直接说结论:无论你 range 的是 a(数组值)还是 *a(数组指针),range 循环内部拿到的**永远是数组元素的副本**,不是原数组或原指针的引用。区别只在于:range *a 会先解引用指针得到数组值,再按该数组做遍历;range a 则直接遍历该值。
常见错误现象:for i := range *a { (*a)[i] = 0 } 看似在改原数组,其实没问题——但如果你写成 for _, v := range *a { v = 0 },v 是副本,改了毫无影响。
-
range a:拷贝整个数组(比如[1000]int就拷 1000 个 int) -
range *a:先解引用*a得到数组值,再拷贝整个数组(开销一样) - 想避免拷贝大数组?必须用索引访问:
for i := range *a { (*a)[i] = ... }
为什么 range *a 不报错,而 range a 在函数参数里常被误用?
因为 Go 允许对指针类型调用 range,前提是它指向一个数组或 slice。但注意:*a 必须是指向数组的指针(如 *[5]int),不是指向 slice 的指针(*[]int)——后者不能 range,会报 cannot range over *[]int (type *[]int)。
使用场景:函数接收 a *[N]T 参数时,最自然的遍历方式就是 for i := range *a 或 for _, v := range *a。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
func f(a *[5]int) { for _, v := range a { ... } }→a是指针,不是可 range 类型 - 正确写法:
for _, v := range *a或for i := 0; i - 性能影响:对大数组,
range *a仍会拷贝整个数组到循环中(仅当用v时);若只读索引,range *a和for i := range *a开销一致
range *a 和 range a[:] 行为一样吗?
不一样。虽然它们都“看起来”在遍历同一个底层数组,但语义和底层行为不同。
a[:] 是把数组转成 slice,range a[:] 遍历的是这个 slice;*a 是显式解引用,range *a 遍历的是数组值本身。关键差异在地址和逃逸分析:
-
range a[:]:slice 头部可能逃逸到堆(尤其当函数内联失败或传递给其他函数时) -
range *a:纯栈操作,不产生额外 slice 头,更轻量 - 二者对元素的修改能力一致:
for i := range *a { (*a)[i] = x }和for i := range a[:] { a[i] = x }效果相同 - 但
a[:]要求a是数组变量名;若a是函数参数*[5]int,就不能直接写a[:](类型不匹配),得写(*a)[:]
最容易踩的坑:以为 range *a 能绕过数组拷贝
不能。Go 的 range 对数组(无论来自 a 还是 *a)总是复制整个数组内容。这不是 bug,是语言设计——数组是值类型。
真实影响:如果你 range 一个 [1e6]byte,哪怕只读 v,每次迭代都会复制 1MB。这时候必须放弃 range,改用索引。
- 错:
for _, b := range *bigArr { use(b) }→ 白拷 1MB - 对:
for i := range *bigArr { use((*bigArr)[i]) }→ 零拷贝 - 也对:传
[]byte而非*[1e6]byte,然后range s(slice range 不拷底层数组)
记住:指针能避免传参拷贝,但 range 自身对数组的处理逻辑不变。别被 * 迷惑了。










