Golang range 实现原理
for-range 是 Golang 提供的一种迭代遍历手段,可操作的类型有 数组、切片、Map、channel
先看题,看以下代码应该输出什么
1-1. 遍历时对原切片 append 是否会无限循环下去
func sliceAppend() {
s := []int{1, 2, 3, 4, 5}
for i := range s {
s = append(s, i)
}
fmt.Println(s) // [1 2 3 4 5 0 1 2 3 4]
// 可以看出并没有无限循环下去
}
2-1. 遍历时修改原数组值,遍历时的 v 取到的值是否是修改后的
func rangeDemoArr() {
var src = [5]int{1, 2, 3, 4, 5}
var dst [5]int
for i, v := range src {
if i == 0 {
src[1] = 12
src[2] = 13
}
dst[i] = v // 根据结果可知这里的 v 并不会取修改后的值
}
fmt.Println("src", src) // src [1 12 13 4 5]
fmt.Println("dst", dst) // dst [1 2 3 4 5]
}
2-2. 遍历时修改原切片值,遍历时的 v 取到的值是否是修改后的
func rangeDemoSlice() {
var src = []int{1, 2, 3, 4, 5}
var dst [5]int
for i, v := range src {
if i == 0 {
src[1] = 12
src[2] = 13
}
dst[i] = v // 根据结果可知这里的 v 会取修改后的值
}
fmt.Println("src", src) // src [1 12 13 4 5]
fmt.Println("dst", dst) // dst [1 12 13 4 5]
}
Go 编译器源码
找到 GCC 版本的编译器代码, range
相关的代码在 statements.cc
...
// Arrange to do a loop appropriate for the type. We will produce
// for INIT ; COND ; POST {
// ITER_INIT
// INDEX = INDEX_TEMP
// VALUE = VALUE_TEMP // If there is a value
// original statements
// }
if (range_type->is_slice_type())
this->lower_range_slice(gogo, temp_block, body, range_object, range_temp,
index_temp, value_temp, &init, &cond, &iter_init,
&post);
else if (range_type->array_type() != NULL)
this->lower_range_array(gogo, temp_block, body, range_object, range_temp,
index_temp, value_temp, &init, &cond, &iter_init,
&post);
else if (range_type->is_string_type())
this->lower_range_string(gogo, temp_block, body, range_object, range_temp,
index_temp, value_temp, &init, &cond, &iter_init,
&post);
else if (range_type->map_type() != NULL)
this->lower_range_map(gogo, range_type->map_type(), temp_block, body,
range_object, range_temp, index_temp, value_temp,
&init, &cond, &iter_init, &post);
else if (range_type->channel_type() != NULL)
this->lower_range_channel(gogo, temp_block, body, range_object, range_temp,
index_temp, value_temp, &init, &cond, &iter_init,
&post);
...
// Lower a for range over an array.
...
// The loop we generate:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
// Set *PINIT to
// var len_temp int
// len_temp = len(range)
// index_temp = 0
...
// Lower a for range over a slice.
...
// The loop we generate:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
//
// Using for_temp means that we don't need to check bounds when
// fetching range_temp[index_temp].
// Set *PINIT to
// range_temp := range
// var len_temp int
// len_temp = len(range_temp)
// index_temp = 0
根据源码中的注释说明可以看出:
遍历 slice 前会先获取 slice 的长度 len_temp
来作为循环次数,并将待循环的 slice 赋值给临时变量 range_temp
。循环体中,每次循环会先获取元素值,如果 for-range 中接收 index
和 value
的话,则会对 index
和 value
进行一次赋值。数组与数组指针的遍历过程与 slice 基本一致。
由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是无法遍历到的。
对于数组:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
value = value_temp
,value_temp
是从 range_temp[index_temp]
中取得的,而 range_temp := range
是值拷贝,range_temp
是一个新的数组,所以循环过程中 range_temp
的值是不变的,所以循环中修改原数组时并不会影响到 value_temp
的值。
所以本文开头的函数 rangeDemoArr
的循环体中的 v
取的到值始终是修改前的,结果 dst
也就与修改前的 src
相同。
对于切片:
// for_temp := range
// len_temp := len(for_temp)
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = for_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }
value = value_temp
,value_temp
是从 for_temp[index_temp]
中取得的,而 for_temp := range
虽然是值拷贝,但由于拷贝的只是切片的结构体,切片中指向的底层数组和原切片还是同一个,所以循环过程中 range_temp
的值和原切片是同一份数据,所以循环中修改原数组时会影响到 value_temp
的值。
所以本文开头的函数 rangeDemoSlice
的循环体中的 v
取的到值始终是修改后的,结果 dst
也就与修改后的 src
相同。
模拟实现简易版 range
// @param _Arr 待遍历数组
// @param rangeBody 遍历代码体
func myRangeArr(_Arr [5]int, rangeBody func(_k int, _v int)) {
len_temp := len(_Arr)
range_temp := _Arr
var _index, _value int
for index_temp := 0; index_temp < len_temp; index_temp++ {
value_temp := range_temp[index_temp]
_index = index_temp
_value = value_temp
rangeBody(_index, _value)
}
}
func rangeDemoArr() {
var src = [5]int{1, 2, 3, 4, 5}
var dst [5]int
myRangeArr(src, func(k, v int) {
if k == 0 {
src[1] = 12
src[2] = 13
}
dst[k] = v
})
fmt.Println("src", src) // src [1 12 13 4 5]
fmt.Println("dst", dst) // dst [1 2 3 4 5]
}
// @param _Slice 待遍历数组
// @param rangeBody 遍历代码体
func myRangeSlice(_Slice []int, rangeBody func(_k int, _v int)) {
for_temp := _Slice
len_temp := len(for_temp)
var _index, _value int
for index_temp := 0; index_temp < len_temp; index_temp++ {
value_temp := for_temp[index_temp]
_index = index_temp
_value = value_temp
rangeBody(_index, _value)
}
}
func rangeDemoSlice() {
var src = []int{1, 2, 3, 4, 5}
var dst [5]int
myRangeSlice(src, func(k, v int) {
if k == 0 {
src[1] = 12
src[2] = 13
}
dst[k] = v
})
fmt.Println("src", src) // src [1 12 13 4 5]
fmt.Println("dst", dst) // dst [1 12 13 4 5]
}
参考资料:
Go Range 内部实现