当前位置: 首页 > news >正文

【golang】探索for-range遍历实现原理(slice、map、channel)

for-range

for-range其实是正常for循环的一种语法糖,在go语言中可以遍历arr,slice,map和channel等数据结构,但是在一些初学者使用for-range可能会遇见很多坑,这篇文章会带你探索一下for-range中非常有趣的一些实现机制。

for-range遍历数组和slice

先来看两道题目:

  1. 从数组中遍历获取一个指针元素切片的集合
arr := [2]int{1, 2}
res := []*int{}
for _, v := range arr {res = append(res, &v)
}
//expect: 1 2
fmt.Println(*res[0],*res[1]) 
//but output: 2 2

答案:从程序中可以看出我们预期输出的为1、2,但实际上却输出了2、2的答案。

  1. 在for-range遍历中append切片
v := []int{1, 2, 3}
for i := range v {v = append(v, i)
}

答案:上面代码遍历是会停止的。

好了,可能有些朋友看完上面两段代码的最终结果已经开始疑惑了,接下来我们来看一下for-range对
数组和slice的处理方法。

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

看上面这段源代码我们可以看出,for-range内部调用其实还是for循环,初始化会拷贝待遍历的列表,然后每次遍历的v都是对上面源码value_temp这同一个元素的赋值。

  1. 这就可以说明我们的题1中为什么输出的会是2、2,对题1v取地址,最终只会拿到一个地址(实际地址:&value_temp),而对应的值就是最后遍历的那个元素所附给v的值。

想得到预期值有两种方案:

  • 使用局部变量
for _, v := range arr {//局部变量v替换了v,也可用别的局部变量名v := v res = append(res, &v)
}
  • 直接索引获取原来的元素
//这种其实退化为for循环的简写
for k := range arr {res = append(res, &arr[k])
}
  1. 题2也就很好说明了,因为遍历次数在遍历前就已经确定下来了(len_temp),所以题2最终只会循环三次。

for-range遍历map

还是先看两道题目:

  1. 对map遍历时删除这个元素,下一次遍历还能遍历到吗?
var m = map[int]int{1: 1, 2: 2, 3: 3}var o sync.Once 
for i := range m {o.Do(func() {for _, key := range []int{1, 2, 3} {if key != i {fmt.Printf("when iteration key %d, del key %d\n", i, key)delete(m, key)break}}})fmt.Printf("%d%d ", i, m[i])
}

答案:如果删除的元素还没有被遍历到(上边once.go函数内保证第一次执行时删除未遍历的一个元素),那么后面就不会出现。因为我们都知道map在for-range遍历中是无序遍历的,这是因为map底层数据结构就是一个链式hash表,并且初始化的时候会随机一个遍历开始的位置,所以如果还没被遍历到的元素已经被删除了,那么后面也肯定不会再出现。

  1. map遍历时新增的元素能被遍历到吗?
var m = map[int]int{1:1, 2:2, 3:3}
for i, _ := range m {m[4] = 4fmt.Printf("%d%d ", i, m[i])
}

答案:输出中可能会有44,原因是因为上一条题目中原因类似(链式hash表,随机遍历开始位置)。

好了,我们再来看一下golang中for-range对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
//   }

遍历map时没有指定循环次数,循环体是和slice类似的。由于map底层实现与slice不同,map底层使用hash表实现,插入数据位置是随机的,所以遍历过程中新插入的数据不能保证被遍历到。

for-range对channel遍历

for-range对channel的处理

// The loop we generate:
//   for {
//           index_temp, ok_temp = <-range
//           if !ok_temp {
//                   break
//           }
//           index = index_temp
//           original body
//   }

channel遍历是依次从channel中读取数据,读取前是不知道里面有多少个元素的。如果channel中没有元素,则会阻塞等待,如果channel已被关闭,则会解除阻塞并退出循环。

注意:

  • 上述注释中index_temp实际上描述是有误的,应该为value_temp,因为index对于channel是没有意义的。
  • 使用for-range遍历channel时只能获取到一个返回值。

相关文章:

  • python科研绘图:圆环图
  • 程序员的绝望和欢笑:当拼写错误搞乱了我的代码
  • 前端设计模式之【代理模式】
  • 【Java 进阶篇】JQuery 遍历 —— For 循环的奇妙之旅
  • react hook ts 实现 列表的滚动分页加载,多参数混合混合搜索
  • ctf之流量分析学习
  • 沉浸式航天vr科普馆VR太空主题馆展示
  • Beautiful Soup爬取数据html xml
  • 查询ip地址
  • Java版B/S架构云his医院信息管理系统源码(springboot框架)
  • 0基础学习VR全景平台篇第121篇:认识视频剪辑软件Premiere
  • 酷开系统 酷开科技,将家庭娱乐推向新高潮
  • 为什么LDO一般不用在大电流场景?
  • AWD比赛中的一些防护思路技巧
  • 设计模式 -- 工厂模式(Factory Pattern)
  • eclipse的离线汉化
  • exports和module.exports
  • Gradle 5.0 正式版发布
  • java多线程
  • Java基本数据类型之Number
  • Service Worker
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 我看到的前端
  • Java性能优化之JVM GC(垃圾回收机制)
  • ​​​​​​​​​​​​​​Γ函数
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • ​一帧图像的Android之旅 :应用的首个绘制请求
  • $jQuery 重写Alert样式方法
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (安卓)跳转应用市场APP详情页的方式
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)springboot 基于HTML5的个人网页的网站设计与实现 毕业设计 031623
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (转)大型网站的系统架构
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • **登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .Net - 类的介绍
  • .NET 8 编写 LiteDB vs SQLite 数据库 CRUD 接口性能测试(准备篇)
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .net 生成二级域名
  • .NET 指南:抽象化实现的基类
  • .Net 中Partitioner static与dynamic的性能对比
  • .net和php怎么连接,php和apache之间如何连接
  • .net中应用SQL缓存(实例使用)
  • ??在JSP中,java和JavaScript如何交互?
  • [ vulhub漏洞复现篇 ] Grafana任意文件读取漏洞CVE-2021-43798
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [bzoj 3534][Sdoi2014] 重建
  • [C++11 多线程同步] --- 条件变量的那些坑【条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)】
  • [flask]http请求//获取请求头信息+客户端信息
  • [FxCop.设计规则]8. 也许参数类型应该是基类型