Golang中for和for range语句的使用技巧、对比及常见的避坑

这篇具有很好参考价值的文章主要介绍了Golang中for和for range语句的使用技巧、对比及常见的避坑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Update1

2024.1.0更新:
Go 团队将修改 for 循环变量的语义,Go1.21 新版本即可体验!
今天看见了这篇文章,Go的1.22版本将更新,大致理解未会默认进行v:=v这个操作,因此此文所概述的许多坑,在1.22之后都可能会更新。
2024.2就会发布新版本,到时候再测试一下看看情况。

前言

基础语法不再赘述,写这个原因是之前的某次面试被问道了,我知道会导致问题但具体答下来不是很通顺。再回想自己开发过程中,很多地方都是使用到了for/for range,但是却从没注意过一些细节,因此专门学习一下进行记录。

对一个数组循环,for range,使用kv时候有什么要注意的吗?
这个是当时面经记录的问题。因此顺着这里开始进行学习。

Golang中for和for range语句的使用技巧、对比及常见的避坑,Golang,golang,开发语言,后端

for和for range基本语法

for的用法大概可以类比C++里面的for和while

//类似C++的for 
for i := 0; i < 10; i++ {
    fmt.Println(i)
}
//类似C++的while
j := 0
for j < 10 {
    fmt.Println(j)
    j++
}

//死循环
for {
    fmt.Println("无限循环")
}

fo range用法大概可以类比python的range。但是golang中他的用法更多,大致四种

//遍历数组:
numbers := [3]int{1, 2, 3}
for index, value := range numbers {
    fmt.Printf("索引:%d 值:%d\n", index, value)
}
//遍历切片:
names := []string{"Alice", "Bob", "Charlie"}
for index, name := range names {
    fmt.Printf("索引:%d 值:%s\n", index, name)
}
//遍历 map:
ages := map[string]int{"Alice": 25, "Bob": 30, "Charlie": 35}
for name, age := range ages {
    fmt.Printf("%s 的年龄是 %d 岁\n", name, age)
}
//遍历字符串:
sentence := "Hello, 世界"
for index, char := range sentence {
    fmt.Printf("索引:%d 字符:%c\n", index, char)
}

for和for range的区别

对于一个数组,例如a[3]={3.2.1},可以通过两种方式进行遍历:


func main() {
	fmt.Println("下面是for range遍历")

	var m = []int{1, 2, 3}
	for k, v := range m {
		fmt.Println(k, v)
		fmt.Println(&k, &v)
	}
	fmt.Println("下面是for遍历")
	for i := 0; i < 3; i++ {
		fmt.Println(i, m[i])
		fmt.Println(&i, &m[i])
	}
}

其结果如下:

Golang中for和for range语句的使用技巧、对比及常见的避坑,Golang,golang,开发语言,后端

我们可以发现,输出的结果是一样的,但地址却不一样:
因此这便是二者的本质区别——
1、range遍历在开始遍历数据之前,会先拷贝一份被遍历的数据。所以在遍历过程中去修改被遍历的数据,只是修改拷贝的数据,不会影响到原数据。
2、range在遍历值类型时,其中的v是一个局部变量,只会声明初始化一次,之后每次循环时重新赋值覆盖前面的。

常见的坑

通过指针取值

//此代码来自`https://zhuanlan.zhihu.com/p/105435646`
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

这个是由于上述的区别2——v是一个只声明一次的局部变量。
修改方案两种:1、通过arr[k]获取值,而不是v。
2、使用一个局部变量:tmp :=v,之后对tmp处理。

循环中添加元素是否会导致死循环

func main() {
 s := []int{0, 1}
 for _, v := range s {
  s = append(s, v)
 }
 fmt.Printf("s=%v\n", s)
}

大致意思是这样会不会死循环?
这个是上面的性质1。range因为是对复制的数据在操作,所以不会影响。普通的for则会影响。

https://cloud.tencent.com/developer/article/1925475
此部分具体参考此例即可。

对大数据的操作

对于很大数据的数组,若采取range遍历,因为复制的缘故,所以会导致开销巨大。(具体见下的性能分析)
但是在大数据的数组下,使用range去重置(全部赋值为0),效率是高的。原因是内存是连续的,编译器会直接清空这一片的内存。具体讲解见此

demo和源码看这里的3和4

对于map的操作

具体来说,就是边遍历边新增/删除。删除了的不可能再遍历到,新增的可能再遍历到(不一定)。

原因:map内部实现是一个链式hash表,为了保证无顺序,初始化时会随机一个遍历开始的位置,所以新增的元素被遍历到就变的不确定了,同样删除也是一个道理,但是删除元素后边就不会出现,所以一定不会被遍历到。

具体参考此文的最后部分

range中开goroutine

func main() {
	
	var m = []int{1, 2, 3}
	for i := range m {
		go func() {
			fmt.Print(i)
		}()
	}
	time.Sleep(time.Millisecond)

}

会发现输出结果有随机性:发现结果是222。原因是上述的那个,k、v是同一值,他并没有保存到goroutine的栈中。
解决方法还是局部变量,但可能出现012、210、102这样的值…原因是运行太快了,所以顺序不一定…

附1:range遍历的性能分析

for range需要进行一步复制操作,因此显然需要比for更多的性能开销。但是对于切片来说,因为切片的副本和原切片都是指向数组的地址,所以性能一样。
具体见此文。

https://blog.csdn.net/EDDYCJY/article/details/124701572

附2:一个坑中坑——map的range遍历并不是随机

具体参考此文

结论是:第一位的元素会有更高的概率被首先选中。因此不可用map遍历的随机性,作为随机选择map中元素的方法。

参考资源

Go 处理大数组:使用 for range 还是 for 循环?
Go的循环遍历使用小坑
Go 中for range的一个坑
Go语言中for-range使用踩坑指南
Dig101 - Go之for-range排坑指南
Golang 语言 for 和 for-range 的区别
5.1 for 和 range #文章来源地址https://www.toymoban.com/news/detail-789681.html

到了这里,关于Golang中for和for range语句的使用技巧、对比及常见的避坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Eclipse常见的使用技巧(快捷键)大全

    注意: 如果你设置了代码提示,那么下面操作快捷方式回车即可生成,不必alt+/ Shift+Enter回车, 不管光标在哪里都会换行! 非常好用! ALT+SHIFT+R 敲入main alt+/ 设置代码提示后sout回车即可 没有设置:敲入syso alt+/ 敲入for alt+/ JDK里提供的类,比如String,Integer,System都是开源的,免

    2024年02月12日
    浏览(35)
  • 异或运算的基本介绍以及使用技巧,剖析常见的异或题目

    异或运算,符号为‘^’,直接对底层二进制串进行运算,比算术运算快得多,规则为:相同为0,不同为1。 假设N为任意实数 性质1:0 ^ N = N 性质2:N ^ N = 0 性质3:异或运算满足交换律与结合律 重点:我们可以将异或运算理解为二进制的无进位相加!也就是说,当两个数异或

    2024年02月08日
    浏览(45)
  • 可以参考Copilot的官方文档和社区,了解更多关于Copilot的使用技巧和常见问题

    在PyCharm中使用Copilot的步骤如下: 获取Copilot的使用权限:首先,确保你拥有一个GitHub的账号。然后,进入Copilot首页,登录你的GitHub账号,并申请使用。几天后,你会收到一封回复邮件,点击邮件中的链接,登录到GitHub。 安装Copilot:在PyCharm中,找到\\\"File\\\"(文件)菜单,然后

    2024年02月02日
    浏览(92)
  • 深入探究for...range语句

    在Go语言中,我们经常需要对数据集合进行遍历操作。对于数组来说,使用for语句可以很方便地完成遍历。然而,当我们面对其他数据类型,如map、string 和 channel 时,使用普通的for循环无法直接完成遍历。为了更加便捷地遍历这些数据类型,Go语言引入了for...range语句。本文将

    2024年02月08日
    浏览(43)
  • 【Golang】Golang进阶系列教程--为什么 Go for-range 的 value 值地址每次都一样?

    循环语句是一种常用的控制结构,在 Go 语言中,除了 for 以外,还有一个 range ,可以使用 for-range 循环迭代数组、切片、字符串、map 和 channel 这些数据类型。 但是在使用 for-range 循环迭代数组和切片的时候,是很容易出错的,甚至很多老司机一不小心都会在这里

    2024年02月15日
    浏览(57)
  • golang中一种不常见的switch语句写法

    最近翻开源代码的时候看到了一种很有意思的switch用法,分享一下。 注意这里讨论的不是 typed switch ,也就是case语句后面是类型的那种。 直接看代码: 你也可以在这找到它:代码链接 简单解释下这段代码在做什么:调用systemctl命令检查指定的服务的运行状态,具体做法是过

    2024年02月02日
    浏览(38)
  • Excel 使用技巧集锦—163种技巧

    目录 一、基本方法7 1.快速选中全部工作表7 2.快速启动E 7 XCEL 3.快速删除选定区域数据 8 4.给单元格重新命名8 5.在E 中选择整个单元格范围9 XCEL 6.快速移动/复制单元格9 7.快速修改单元格式次序 9 8.彻底清除单元格内容10 9.选择单元格10 10. 为工作表命名11 11. 一次性打开多个工作

    2024年02月03日
    浏览(48)
  • postman使用技巧

    add 新建参数:ssl_token ## 添加cookie

    2023年04月13日
    浏览(81)
  • MAC电脑使用技巧

    mac 上怎么显示隐藏的/user文件夹,有两种方法可选~~~ 1,Finder界面是,最上方,通过“前往”进入“电脑”或文件夹 先进入到需要显示隐藏文件的文件夹下 接着按Command苹果键+F,在窗格上会显示搜索栏 然后将第一个下列选择项“种类kind”选择为“其它Other” 当选择“其它”时

    2024年02月11日
    浏览(43)
  • WPS表格:使用技巧

    链接:excel中的日期格式 ① 连接符 只有 \\\"/\\\"和\\\"-\\\" 是日期格式的连接符 将且仅将选中的单元格设置为“短日期” ,输入2000 / 1 / 1或2000 - 1 - 1会自动转换为2000 / 1 / 1 将且仅将选中的单元格设置为“长日期” ,输入2000 / 1 / 1或2000 - 1 - 1会自动转换为2000 年 1 月 1 日 若是其它符号

    2024年02月09日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包