go range 原理分析
range为我们遍历数组,切片等复杂数据类型提供了方便,但range只不过是语法糖而已,本质还是for循环。
我们通过几个简单的例子来引出range的实现原理
例子1
a := [5]int{1,2,3,4,5} for k,v := range a{ if k == 0{ a[2] = 100 } fmt.Println(k,v) } fmt.Println(a)
例子2
b := []int{1,2,3,4} for k,v := range b{ if k == 0{ b[2] = 100 b = append(b,5) } fmt.Println(k,v) } fmt.Println(b)
例子3
c := map[string]int{ "a" : 1, "b" : 2, } for k,v := range c{ if k == "a"{ c["b"] = 100 } fmt.Println(k,v) } fmt.Println(c)
我们来揭晓答案
例子1
0 1 1 2 2 3 3 4 4 5 [1 2 100 4 5]
例子2
0 1 1 2 2 100 3 4 [1 2 100 4 5]
例子3
a 1 b 100 map[a:1 b:100]
接下来我们来分析一下
range会复制对象学习range的时候看到这句高亮的话,复制对象,复制的是谁?切片的底层数组是否会复制?
通过例子1,我们可以猜测,range会复制一个全新的数组,所以我们在循环里修改数组a的值并不会影响循环本身,因为循环使用的是数组a的拷贝。事实上确实如此,稍等我会贴出底层代码。
那例子2就很奇怪,这也是大多数学习range的时候困惑的地方。那按照例子1去理解,我修改了切片b的长度,是不会影响循环的次数,因为循环的是拷贝嘛(其实原因并非仅仅如此)。为何改了元素的值却影响了循环呢?
range只是语法糖,本质是for循环编译的时候range会转成for循环,基于不同的数据结构,for循环也不一样,咱们来看看底层代码。
遍历数组
// The loop e 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 // }
我们能得到什么信息呢
1.遍历的range_temp 是数组的拷贝,而非原数组
2.key 和 value (索引和值),是在循环的时候进行赋值的。
3.循环次数在循环之前就已经计算好的。
遍历切片
// 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 // }
和数组遍历基本一样。需要注意的是,这里的for_temp并非数组的拷贝,而是切片的拷贝,原切片以及for_temp指向的是相同的底层数组。
由于len_temp是在循环前计算好的,所以修改原切片的长度并不会影响循环的次数,(其实循环的是for_temp切片,原切片长度怎么变都不会影响for_temp切片的长度)。
for_temp和原切片指向的是相同的底层数组,所以修改原切片的值,for_temp取出的值也会变。
知道了 range的底层实现,再来看一个面试题常出的例子
b := []int{1,2,3,4,5} m := map[int]int{} for k,v := range b{ m[k] = &v } for _,v := range m{ println(v) }
会输出什么呢?
答案是
5 5 5 5 5
有没有出乎你的意料。其实通过底层代码我们是能推倒出来的,index 和 value 是值拷贝,而非引用,每次循环会重新赋值。
还原成for循环代码如下
b := []int{1,2,3,4,5} m := map[int]int{} lenB := len(b) var k int var v int for i:=0;ik和v的内存地址始终没变,变得是值,map里存的5项都是同一个内存地址。一次循环 v= 5,所以循环map的时候都是5.
那如果把代码改一下呢
b := []int{1,2,3,4,5} m := map[int]int{} lenB := len(b) for i:=0;i输出什么,应该很容易分析出来了
附一下遍历map的源码
// Loer a for range over a map. // The loop e generate: // var hiter map_iteration_struct // for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) { // index_temp = hiter.key // value_temp = hiter.val // index = index_temp // value = value_temp // original body // }
空调维修
- 海信电视维修站 海信电视维修站点
- 格兰仕空调售后电话 格兰仕空调维修售后服务电
- 家电售后服务 家电售后服务流程
- 华扬太阳能维修 华扬太阳能维修收费标准表
- 三菱电机空调维修 三菱电机空调维修费用高吗
- 美的燃气灶维修 美的燃气灶维修收费标准明细
- 科龙空调售后服务 科龙空调售后服务网点
- 华帝热水器维修 华帝热水器维修常见故障
- 康泉热水器维修 康泉热水器维修故障
- 华凌冰箱维修电话 华凌冰箱维修点电话
- 海尔维修站 海尔维修站点地址在哪里
- 北京海信空调维修 北京海信空调售后服务
- 科龙空调维修 科龙空调维修故障
- 皇明太阳能售后 皇明太阳能售后维修点
- 海信冰箱售后服务 海信冰箱售后服务热线电话
- 海尔热水器服务热线