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;i
k和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
// }
空调维修
- 我的世界电脑版运行身份怎么弄出来(我的世界
- 空调抽湿是什么意思,设置抽湿的温度有什么意
- 方太燃气灶有一个打不着火 怎么修复与排查方法
- 夏季免费清洗汽车空调的宣传口号
- 清洗完空调后出现漏水现象
- iphone6能玩什么游戏(iphone6游戏)
- 如何设置电脑密码锁屏(如何设置电脑密码锁屏
- win10删除开机密码提示不符合密码策略要求
- 电脑w7显示不是正版(w7不是正版怎么解决)
- 万家乐z8热水器显示e7解决 怎么修复与排查方法
- 1匹空调多少瓦数(1匹空调多少瓦)
- 安卓手机连接电脑用什么软件好(关于安卓手机
- 电脑网页看视频卡是什么原因(爱拍看视频卡)
- 华帝燃气灶点火器一直响然后熄火怎么办:问题
- 电脑壁纸怎么换(关于电脑壁纸怎么换的介绍)
- 冬天空调的出风口应该朝什么方向(冬天空调风