Go语言遍历切片时切片扩容,为何输出不符合预期?
Go切片for range遍历的疑问
问题场景
我使用for range遍历如下切片代码时,得到的输出为:0A,1M,2C,。
代码:
x := []string{"A", "B", "C"} for i, s := range x { print(i, s, ",") x[i+1] = "M" x = append(x, "Z") x[i+1] = "Z" }
为何输出是这样而非0A, 1Z, 2Z?查阅文档得知切片传参基于底层数组指针,那切片传给for range循环也是传指针吗?我猜测切片扩容后,print的(i,s)仍基于原底层数组,该猜测是否正确?
核心原因解析
1. for range遍历切片的本质:复制切片结构体
Go里的切片是包含指针(指向底层数组)、长度、容量的结构体。当用for range遍历切片时,会先复制一份这个切片结构体,后续遍历的是这份复制后的切片,而非原切片本身。
这意味着:
- 遍历的索引
i和值s,完全基于复制后的切片初始长度(这里是3)生成,循环只会执行3次,不会因为原切片append扩容而增加循环次数。 - 每次循环的
s,是对应索引位置元素的副本,而非引用。
2. 输出结果逐次拆解
第一次循环(i=0, s="A")
- 先打印
0A, - 执行
x[i+1] = "M":此时原切片未扩容,直接修改原底层数组的索引1位置为"M" - 执行
x = append(x, "Z"):原切片容量为3,append后长度变为4,触发扩容(string切片扩容后容量变为6),此时原切片指向了新的底层数组 - 执行
x[i+1] = "Z":修改的是新底层数组的索引1位置为"Z",但for range遍历用的是复制后的切片(仍指向原底层数组),所以不影响后续遍历的s
第二次循环(i=1, s="M")
- 这里的
s是从原底层数组的索引1位置取的(也就是之前修改的"M"),所以打印1M, - 执行
x[i+1] = "M":修改新底层数组的索引2位置为"M" - 执行
x = append(x, "Z"):新切片长度变为5,容量保持6不变 - 执行
x[i+1] = "Z":修改新底层数组的索引2位置为"Z",同样不影响原底层数组
第三次循环(i=2, s="C")
s来自原底层数组的索引2位置,值还是初始的"C",所以打印2C,- 此时
i+1为3,修改的是新数组的索引3位置,原底层数组的索引2位置从未被改动,因此不影响当前的s
3. 你的猜测验证
你的猜测部分正确:for range遍历的是初始切片的复制体,它指向原底层数组,即使原切片后续扩容指向新数组,遍历过程中取的s仍来自原底层数组的元素。但要明确:for range不是传指针,而是复制了整个切片结构体,这才是问题的核心。
内容的提问来源于stack exchange,提问作者Тимур Имашев
相关产品推荐
相关产品推荐

