您现在的位置是:群英 > 开发技术 > 编程语言
Go的循环遍历使用有哪些是需要注意的
Admin发表于 2022-05-23 17:51:59773 次浏览
这篇文章主要给大家介绍“Go的循环遍历使用有哪些是需要注意的”的相关知识,下文通过实际案例向大家展示操作过程,内容简单清晰,易于学习,有这方面学习需要的朋友可以参考,希望这篇“Go的循环遍历使用有哪些是需要注意的”文章能对大家有所帮助。


在Golang的流程控制中,循环语句有for和range两种。

for语句

1.for 赋值表达式; 关系表达式或逻辑表达式; 赋值表达式 { }

for i := 0; i < 10; i++ {}

2.for 关系表达式或逻辑表达式 { }

n := 10for n > 0 {
 n--}

3.for { }

for {
 fmt.Println("hello world")
}
// 等价于
// for true {
//     fmt.Println("hello world")
// }

range语句

Golang range类似迭代器操作,可以对 slice、map、数组、字符串等进行迭代循环。在字符串、数组和切片中它返回 (索引, 值) ,在集合中返回 (键, 值),但若当只有一个返回值时,第一个参数是索引或键。

str := "abc"
for i, char := range str {
    fmt.Printf("%d => %s\n", i, string(char))
}
for i := range str { //只有一个返回值
    fmt.Printf("%d\n", i)
}
nums := []int{1, 2, 3}
for i, num := range nums {
    fmt.Printf("%d => %d\n", i, num)
}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
    fmt.Printf("%s => %s\n", k, v)
}
for k := range kvs { //只有一个返回值
    fmt.Printf("%s\n", k)
}
// 输出结果
// 0 => a
// 1 => b
// 2 => c
// 0
// 1
// 2
// 0 => 1
// 1 => 2
// 2 => 3
// a => apple
// b => banana
// a
// b

for循环尤其是range语句,在平时开发过程中频繁使用,但很多开发者(本人算一个)经常会在以下场景中踩坑。

场景一,使用循环迭代器的变量

先来看一个明显的错误:

func main() {
    var out []*int
    for i := 0; i < 3; i++ {
        // i := i
        out = append(out, &i)
    }
    fmt.Println("值:", *out[0], *out[1], *out[2])
    fmt.Println("地址:", out[0], out[1], out[2])
}
// 输出结果
// 值: 3 3 3
// 地址: 0xc000012090 0xc000012090 0xc000012090

分析

out是一个整型指针数组变量,在for循环中,声明了一个i变量,每次循环将i的地址追加到out切片中,但是每次追加的其实都是i变量,因此我们追加的是一个相同的地址,而该地址最终的值是3。

正确做法

解开代码中的注释// i := i,每次循环时都重新创建一个新的i变量。

再看一个比较隐秘的错误:

func main() {
    a1 := []int{1, 2, 3}
    a2 := make([]*int, len(a1))

    for i, v := range a1 {
        a2[i] = &v
    }

    fmt.Println("值:", *a2[0], *a2[1], *a2[2])
    fmt.Println("地址:", a2[0], a2[1], a2[2])
}
// 输出结果
// 值: 3 3 3
// 地址: 0xc000012090 0xc000012090 0xc000012090

分析

大多数人就是在range这里给变量赋值的时候踩坑,因为比较隐秘,其实情况和上面的一样,range在遍历值类型时,其中的v是一个局部变量,只会声明初始化一次,之后每次循环时重新赋值覆盖前面的,所以给a2[i]赋值的时候其实都是同一个地址&v,而v最终的值为a1最后一个元素的值,也就是3。

正确做法

a2[i]赋值时传递原始指针,即a2[i] = &a1[i]
②创建临时变量t := va2[i] = &t
③闭包(与②原理一样),func(v int) { a2[i] = &v }(v)

更为隐秘的还有:

func main() {
    var out [][]int
    for _, i := range [][1]int{{1}, {2}, {3}} {
        out = append(out, i[:])
    }
    fmt.Println("Values:", out)}// 输出结果// [[3] [3] [3]]

原理也是一样的,不论遍历多少次,i[:]总是被本次遍历的值所覆盖

场景二,在循环体内使用goroutines

func main() {
    values := []int{1, 2, 3}
    wg := sync.WaitGroup{}
    for _, val := range values {
        wg.Add(1)
        go func() {
            fmt.Println(val)
            wg.Done()
        }()
    }
    wg.Wait()}// 输出结果// 3// 3// 3

分析

对于主协程来讲,循环是很快就跑完的,而这个时候各个协程可能才开始跑,此时val的值已经遍历到最后一个了,所以各协程都输出了3。(如果遍历数据庞大,主协程遍历耗时较久的话,goroutine的输出会根据当时候的val的值,所以每次的输出结果不一定相同的。)

解决办法

①使用临时变量

for _, val := range values {
    wg.Add(1)
    val := val    go func() {
        fmt.Println(val)
        wg.Done()
    }()}

②使用闭包

for _, val := range values {
    wg.Add(1)
    go func(val int) {
        fmt.Println(val)
        wg.Done()
    }(val)}

以上就是关于Go的循环遍历使用有哪些是需要注意的的介绍啦,需要的朋友可以参考上述内容,希望对大家有帮助,欢迎关注群英网络,小编将为大家输出更多高质量的实用文章!

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

标签: go
相关信息推荐
2022-08-19 17:48:41 
摘要:这篇文章主要介绍了Python实现字符串格式化输出的方法,结合具体实例形式总结分析了Python字符串格式化输出的各种常用操作技巧,需要的朋友可以参考下
2021-11-04 17:40:41 
摘要:这篇文章给大家分享的是PHP异常处理机制的内容,一些朋友可能对于PHP异常处理机制还不是很理解,对此本文有很详细的介绍,包括对异常的概念,异常的基本使用等等,感兴趣的朋友接下来跟随小编一起学习一下吧。
2022-10-09 18:13:59 
摘要:中缀表达式是一个通用的算术或逻辑公式表示方法。,中缀表达式不容易被计算机解析,但仍被许多程序语言使用,因为它符合人们的普遍用法。本文介绍了实现中缀表达式的方法,需要的可以参考一下
云活动
推荐内容
热门关键词
热门信息
群英网络助力开启安全的云计算之旅
立即注册,领取新人大礼包
  • 联系我们
  • 24小时售后:4006784567
  • 24小时TEL :0668-2555666
  • 售前咨询TEL:400-678-4567

  • 官方微信

    官方微信
Copyright  ©  QY  Network  Company  Ltd. All  Rights  Reserved. 2003-2019  群英网络  版权所有   茂名市群英网络有限公司
增值电信经营许可证 : B1.B2-20140078   粤ICP备09006778号
免费拨打  400-678-4567
免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
微信公众号
返回顶部
返回顶部 返回顶部