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 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
// }
我们能得到什么信息呢
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的源码
// Lower a for range over a map.
// The loop we 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
// }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)