• 【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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

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

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

    好了,可能有些朋友看完上面两段代码的最终结果已经开始疑惑了,接下来我们来看一下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
    //   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    看上面这段源代码我们可以看出,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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 直接索引获取原来的元素
    //这种其实退化为for循环的简写
    for k := range arr {
        res = append(res, &arr[k])
    }
    
    • 1
    • 2
    • 3
    • 4
    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])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    答案:如果删除的元素还没有被遍历到(上边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] = 4
        fmt.Printf("%d%d ", i, m[i])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    答案:输出中可能会有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
    //   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    遍历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
    //   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

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

    注意:

    • 上述注释中index_temp实际上描述是有误的,应该为value_temp,因为index对于channel是没有意义的。
    • 使用for-range遍历channel时只能获取到一个返回值。
  • 相关阅读:
    【JavaWeb】Servlet系列——使用纯Servlet做一个单表的CRUD操作(实际操作实现篇)
    MST56XXB 60V,150mA,2.1uA,高PSRR,高压低压差线性稳压器
    LIO-EKF: 运行数据UrbanNav与mid360设备详细教程
    D. Balanced Round
    机房环境监控什么意思?机房环境监控系统作用
    JVM之类加载子系统
    Holographic MIMO Surfaces (HMIMOS)以及Reconfigurable Holographic Surface(RHS)仿真
    【论文精读】Understanding Open Ports in Android Applications
    springBoot集成SpringSecurity(随笔记录)
    UWB芯片与模块市场的崛起与趋势
  • 原文地址:https://blog.csdn.net/m0_53328239/article/details/134385988