go range 原理分析

go range 原理分析,第1张

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
//   }

欢迎分享,转载请注明来源:内存溢出

原文地址: http://www.outofmemory.cn/langs/996378.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)