Golang 里的 range 问题
Golang 类似于 C/C++,同时又从其他语言(比如 Python)里借鉴了一些语法,比如 range。
在使用上,range 有一些特性,如果不注意的话,很容易引起误解。比如以下这段代码,会不会一直循环下去?
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
}
先说结论:不会,循环运行 3 次后结束。
细节解释:
range
时,先对 v 进行了一次浅拷贝(包括指向底层数组某个偏移位置的指针、切片元素的长度、切片可以使用的总长度等),并用这份拷贝来进行 for 的 3 次循环;- 第一次
append
时,因为底层数组的容量不够,所以会分配更大的(由运行时的算法决定的)内存,把原有数据拷贝进去,并通过返回值更新 v 的内容,也就是说,这时 v 已经有了新的指向。(当然,在刚开始时,v 还是指向了原来的内存,所以这时可以对原来的数组进行访问和修改); - 第二次
append
时,如果当前 v 指向的的底层数组足够大,那么直接追加;否则类似上一个步骤里的做法; - 第三次
append
时,同上一步; - 循环结束;
可以通过这样的代码,来跟踪具体的运行过程,特别是 v 被重新分配时候:
func main() {
v := []int{1, 2, 3}
for i := range v {
+ fmt.Printf("v: len:%d, cap:%d\n", len(v), cap(v))
v = append(v, i)
+ fmt.Printf("v: len:%d, cap:%d\n", len(v), cap(v))
}
+ fmt.Printf("v: %v, len:%d, cap:%d\n", v, len(v), cap(v))
}
运行结果(实际的容量变化取决于 go 运行时的算法):
v: len:3, cap:3
v: len:4, cap:6
v: len:4, cap:6
v: len:5, cap:6
v: len:5, cap:6
v: len:6, cap:6
v: [1 2 3 0 1 2], len:6, cap:6
所以,需要注意 range 时的那次浅拷贝,以及切片在 append 时可能引起的底层数组的重新分配。
以下是一些参考文章,里面提到的内容,对于深入理解 range 的实现及应用很有帮助: