跳至主要內容

Golang range 实现原理

安格原创golanggolang大约 6 分钟...

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.ccopen in new window

...
   // 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 中接收 indexvalue 的话,则会对 indexvalue 进行一次赋值。数组与数组指针的遍历过程与 slice 基本一致。
由于循环开始前循环次数就已经确定了,所以循环过程中新添加的元素是无法遍历到的。

对于数组open in new window:

  //   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_tempvalue_temp 是从 range_temp[index_temp] 中取得的,而 range_temp := range 是值拷贝,range_temp 是一个新的数组,所以循环过程中 range_temp 的值是不变的,所以循环中修改原数组时并不会影响value_temp 的值。
所以本文开头的函数 rangeDemoArr 的循环体中的 v 取的到值始终是修改前的,结果 dst 也就与修改前src 相同。

对于切片open in new window:

  //   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_tempvalue_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 内部实现open in new window

上次编辑于:
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3