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 // }
空调维修
- 温岭冰箱全国统一服务热线-全国统一人工【7X2
- 荆州速热热水器维修(荆州热水器维修)
- 昆山热水器故障码5ER-昆山热水器故障码26
- 温岭洗衣机24小时服务电话—(7X24小时)登记报
- 统帅热水器售后维修服务电话—— (7X24小时)登
- 阳江中央空调统一电话热线-阳江空调官方售后电
- 乌鲁木齐阳春燃气灶厂家服务热线
- 珠海许昌集成灶售后服务电话-全国统一人工【
- 乌鲁木齐中央空调维修服务专线-乌鲁木齐中央空
- 新沂热水器故障电话码维修-新沂热水器常见故障
- 诸城壁挂炉24小时服务热线电话
- 靖江空调24小时服务电话-——售后维修中心电话
- 空调室外滴水管维修(空调室外排水管维修)
- 九江壁挂炉400全国服务电话-(7X24小时)登记报修
- 热水器故障码f.22怎么解决-热水器f0故障解决方法
- 营口热水器售后维修服务电话—— 全国统一人工