下面的代码是死循环么?

  1. func main() {
  2. v := []int{1, 2, 3}
  3. for i := range v {
  4. v = append(v, i)
  5. }
  6. }

上面的代码先初始化了一个内容为1、2、3的slice,然后遍历这个slice,然后给这个切片追加元素。随着遍历的进行,数组v也在逐渐增大,那么这个for循环是一个死循环么?
答案是否。只会遍历三次,并不是死循环,原因就在于for range实现的时候用到了语法糖。

语法糖

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·蘭丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。 语法糖让程序更加简洁,有更高的可读性。 对于切片的for range,它的底层代码就是:

  1. // for_temp := range
  2. // len_temp := len(for_temp)
  3. // for index_temp = 0; index_temp < len_temp; index_temp++ {
  4. // value_temp = for_temp[index_temp]
  5. // index = index_temp
  6. // value = value_temp
  7. // original body
  8. // }

可以看到,在遍历之前就获取的切片的长度len_temp := len(for_temp),遍历的次数不会随着切片的变化而变化,上面的代码自然不会是死循环了。

下面的代码有什么问题么?

  1. slice := []int{0, 1, 2, 3}
  2. myMap := make(map[int]*int)
  3. for index, value := range slice {
  4. fmt.Println(&index, &value)
  5. myMap[index] = &value
  6. }
  7. fmt.Println("=====new map=====")
  8. for k, v := range myMap {
  9. fmt.Printf("%d => %d\n", k, *v)
  10. }

这也是实际编码中有可能会遇到的问题,循环切片,index和value地址最开始进行一次分配,在整个for循环中会一直使用,遍历操作只是不断的覆盖 index与value地址中保存的值,把切片值的地址保存到myMap中,这样的操作结果是:

  1. 0xc00000a0c0 0xc00000a0c8
  2. 0xc00000a0c0 0xc00000a0c8
  3. 0xc00000a0c0 0xc00000a0c8
  4. 0xc00000a0c0 0xc00000a0c8
  5. == ===new map=====
  6. 0 => 3
  7. 1 => 3
  8. 2 => 3
  9. 3 => 3

结果完全一样,都是最后一次遍历的值。通过上面的底层代码看下,遍历后的值赋给了value,而在我们的例子中,会把value的地址保存到myMap的值中。这里的value是个「全局变量」,所以赋完值之后myMap里面所有的值都是value,所以结构都是一样的而且是最后一个值。
注意,这里必须是保存指针才会有问题,如果直接保存的是value,因为 Golang 是值拷贝,所以值会重新复制再保存,这种情况下结果就会是正确的了。

切片For Range原理

总结一下,通过For Range遍历切片, 首先,计算遍历次数(切片长度);每次遍历,都会把当前遍历到的值存放到一个全局变量index中。

其它语法糖

另外,For Range 不光支持切片。其它的语法糖底层代码。

map

  1. // Lower a for range over a map.
  2. // The loop we generate:
  3. // var hiter map_iteration_struct
  4. // for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) {
  5. // index_temp = *hiter.key
  6. // value_temp = *hiter.val
  7. // index = index_temp
  8. // value = value_temp
  9. // original body
  10. // }

channel

  1. // Lower a for range over a channel.
  2. // The loop we generate:
  3. // for {
  4. // index_temp, ok_temp = <-range
  5. // if !ok_temp {
  6. // break
  7. // }
  8. // index = index_temp
  9. // original body
  10. // }

数组

  1. // Lower a for range over an array.
  2. // The loop we generate:
  3. // len_temp := len(range)
  4. // range_temp := range
  5. // for index_temp = 0; index_temp < len_temp; index_temp++ {
  6. // value_temp = range_temp[index_temp]
  7. // index = index_temp
  8. // value = value_temp
  9. // original body
  10. // }

字符串

  1. // Lower a for range over a string.
  2. // The loop we generate:
  3. // len_temp := len(range)
  4. // var next_index_temp int
  5. // for index_temp = 0; index_temp < len_temp; index_temp = next_index_temp {
  6. // value_temp = rune(range[index_temp])
  7. // if value_temp < utf8.RuneSelf {
  8. // next_index_temp = index_temp + 1
  9. // } else {
  10. // value_temp, next_index_temp = decoderune(range, index_temp)
  11. // }
  12. // index = index_temp
  13. // value = value_temp
  14. // // original body
  15. // }

参考链接:https://segmentfault.com/a/1190000023477520