经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
简单聊聊Go?for?range中容易踩的坑
来源:jb51  时间:2023/3/22 9:23:24  对本文有异议

前言

为了让大家更好的理解本期知识点,先介绍以下几个知识点:线性结构、非线性结构、循环、迭代、遍历、递归。

线性结构:数组、队列

非线性结构:树、图

循环(loop):最基础的概念,所有重复的行为都是循环

递归(recursion):在函数内调用自身,将复杂情况逐步转化成基本情况

(数学)迭代(iterate):在多次循环中逐步接近结果

(编程)迭代(iterate):按顺序访问线性结构中的每一项

遍历(traversal):按规则访问非线性结构中的每一项

下面会挑选几个经典的案例,一块来探讨下,看看如何避免掉坑,多积累积累采坑经验。

1. for+传值

先来到开胃菜,热热身~

  1. type?student?struct?{
  2. ??name?string
  3. ??age??int
  4. }
  5.  
  6. func?main()?{
  7. ??m?:=?make(map[string]student)
  8. ??stus?:=?[]student{
  9. ????{name:?"张三",?age:?18},
  10. ????{name:?"李四",?age:?23},
  11. ????{name:?"王五",?age:?26},
  12. ??}
  13. ??for?_,?stu?:=?range?stus?{
  14. ????m[stu.name]?=?stu
  15. ??}
  16. ??for?k,?v?:=?range?m?{
  17. ????fmt.Println(k,?"=>",?v.name)
  18. ??}
  19. }

不出意料,输出结果为

李四 => 李四
王五 => 王五
张三 => 张三

这题比较简单,就是简单的传值操作,大家应该都能答上来。下面加大难度,改为传址操作

2. for+传址

将案例一改为传址操作

  1. type?student?struct?{
  2. ??name?string
  3. ??age??int
  4. }
  5.  
  6. func?main()?{
  7. ??m?:=?make(map[string]*student)
  8. ??stus?:=?[]student{
  9. ????{name:?"张三",?age:?18},
  10. ????{name:?"李四",?age:?23},
  11. ????{name:?"王五",?age:?26},
  12. ??}
  13. ??for?_,?stu?:=?range?stus?{
  14. ????m[stu.name]?=?&stu
  15. ??}
  16. ??for?k,?v?:=?range?m?{
  17. ????fmt.Println(k,?"=>",?v.name)
  18. ??}
  19. }

好好想想应该输出什么结果呢?还是跟案例一是一样的结果吗?难道会有坑?

不出意料,还是出了意外,输出结果为

张三 => 王五
李四 => 王五
王五 => 王五

为什么呢?

  • 首先,关键点在于Go的for循环,对循环变量stu每次是循环并不是迭代(简单的说,就是对循环变量stu只会做一次声明和内存地址的分配,后面循环就是不断更新值);
  • 所以,取址操作 &stu,其实都是取的同一个变量的地址,只是值被循环更新为最后一个元素的值;
  • 最终,输出的v.name,都是最后一个元素的name为王五

解决方案

在for循环中,做同名变量覆盖stu:=stu(即重新声明一个局部变量,做值拷贝,避免相互影响)

  1. type?student?struct?{
  2. ??name?string
  3. ??age??int
  4. }
  5.  
  6. func?main()?{
  7. ??m?:=?make(map[string]*student)
  8. ??stus?:=?[]student{
  9. ????{name:?"张三",?age:?18},
  10. ????{name:?"李四",?age:?23},
  11. ????{name:?"王五",?age:?26},
  12. ??}
  13. ??for?_,?stu?:=?range?stus?{
  14. ????stu?:=?stu??//同名变量覆盖
  15. ????m[stu.name]?=?&stu
  16. ??}
  17. ??for?k,?v?:=?range?m?{
  18. ????fmt.Println(k,?"=>",?v.name)
  19. ??}
  20. }

输出结果:

张三 => 张三
李四 => 李四
王五 => 王五

3.for+闭包

在for循环里,做闭包操作,也是很容易掉坑的。看看下面输出什么?

  1. var?prints?[]func()
  2. for?_,?v?:=?range?[]int{1,?2,?3}?{
  3. ??prints?=?append(prints,?func()?{?fmt.Println(v)?})
  4. }
  5. for?_,?print?:=?range?prints?{
  6. ??print()
  7. }

一眼看过去,感觉是输出1 2 3,但实际会输出 3 3 3

为什么呢?

  • 首先,在分析了案例二后,我们知道了Go的for循环对循环变量v,其实每次是循环并不是迭代;
  • 然后,闭包=函数+引用环境,在同一个引用环境下,循环变量v的值会被不断的覆盖;
  • 所以最终,在打印时,输出的v,都是最后一个值3。

解决方案

和案例二解决方案一样,是在for循环中,做同名变量覆盖v:=v

  1. var?prints?[]func()
  2. for?_,?v?:=?range?[]int{1,?2,?3}?{
  3. ??v?:=?v?//同名变量覆盖??
  4. ??prints?=?append(prints,?func()?{?fmt.Println(v)?})
  5. }
  6. for?_,?print?:=?range?prints?{
  7. ??print()
  8. }

输出结果:

1
2
3

4. for+goroutine

在for循环里,起goroutine协程,也是很迷惑很容易掉坑的。看看下面输出什么?

  1. var?wg?sync.WaitGroup
  2. strs?:=?[]string{"1",?"2",?"3",?"4",?"5"}
  3. for?_,?str?:=?range?strs?{
  4. ??wg.Add(1)
  5. ??go?func()?{
  6. ????defer?wg.Done()
  7. ????fmt.Println(str)
  8. ??}()
  9. }
  10. wg.Wait()

一眼看过去,感觉是会无序输出1 2 3 4 5,但实际会输出 5 5 5 5 5

为什么呢?

  • 首先,要记得Go的for循环对循环变量str,其实每次是循环并不是迭代;
  • 然后,main协程会和新起的协程做相互博弈,看谁执行更快,按这个案例执行情况来看,main协程执行速度明显比新起的协程会更快,所以str被更新为最后一个元素值5(备注:并非绝对);
  • 最终,在新起的协程中,使用str时值都为5,作为结果去输出;
  • 拓展:如果在新起协程前,sleep个5s,输出结果又会截然不同,感兴趣的同学可以自行实验下,然后逐步深入地了解下GMP调度机制

解决方案

和前面两个案例解决方案一样,是在for循环中,做同名变量覆盖str:=str

  1. var?wg?sync.WaitGroup
  2. strs?:=?[]string{"1",?"2",?"3",?"4",?"5"}
  3. for?_,?str?:=?range?strs?{
  4. ??str?:=?str?//同名变量覆盖
  5. ??wg.Add(1)
  6. ??go?func()?{
  7. ????defer?wg.Done()
  8. ????fmt.Println(str)
  9. ??}()
  10. }
  11. wg.Wait()

输出结果:

5
4
2
1
3

注意是1~5无序输出

总结

for循环中做传址、闭包、goroutine相关操作,千万要注意,一不小心就会很容易掉坑。

使用好同名变量覆盖v:=v,这个解决大法,能很便捷的解决这一类问题。

到此这篇关于简单聊聊Go for range中容易踩的坑的文章就介绍到这了,更多相关Go for range内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号