golang map遍历

· · 11962 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

map 遍历

遍历的顺序是随机的

基本的遍历方式

func main() {
    m := map[string]int{
        "a": 1,
        "b": 2,
    }

    for k, v := range m {
        fmt.Println(k, v)
    }
}
/* 可能的输出之一
    a 1
    b 2
*/

/* 可能的输出之二
    b 2
    a 1
*/

遍历的时候元素的顺序是随机的,有可能先输出a,也有可能先输出b

在遍历中增加元素

在遍历的同时增加元素,增加的元素是可以被遍历到的

func main() {
    m := map[string]int{
        "a": 1,
        "b": 2,
    }
    for k, v := range m {
        m["c"] = 3
        fmt.Println(k, v)
    }
}
/* 输出
    b 2
    c 3 // 这个是在遍历中增加的元素,
    a 1
*/

在遍历中删除元素

如果还没有遍历到某个元素的时候,就在遍历的过程中删除这个元素,那么后续的遍历是遍历不到这个元素的

func main() {
    m := map[string]int{
        "a": 1,
        "b": 2,
    }

    n := 0
    flag := false
    for k, v := range m {
        if !flag {
            flag = true
            if k == "a" {
                delete(m, "b")
            } else {
                delete(m, "a")
            }
        }
        n++
        fmt.Println(n, k, v)
    }
}
/* 可能的输出之一
    1 a 1
*/

/* 可能的输出之二
    1 b 2
*/

map的的遍历是随机的,因此上面的代码的作用是,如果第一次遍历的元素是a,那么删除b;如果第一次遍历的元素是b,那么删除a。两种可能的情况下,都只有一个输出,这说明,在遍历的时候删除了元素,那么后续就遍历不到了。

map遍历易错点

使用for range遍历的时候,k,v使用的同一块内存,这是容易出现错误的地方

func main() {
    m := map[string]int{
        "a": 1,
        "b": 2,
    }

    sKey := []string{}
    sValue := []*int{}

    {
        fmt.Println("---- range ---- start")
        for k, v := range m {
            fmt.Printf("key:%p,%v, value:%p,%v\n", &k, k, &v, v)
            sKey = append(sKey, k)      // 保存的是k的值
            sValue = append(sValue, &v) // 保存的是v的指针
        }
        fmt.Println("---- range ---- end\n")
    }

    {
        // 打印出 sKey 中保存的元素,以及实际指向的值
        fmt.Println("---- sKey ---- start")
        fmt.Printf("%p, %v\n", &sKey[0], sKey[0])
        fmt.Printf("%p, %v\n", &sKey[1], sKey[1])
        fmt.Println("---- sKey ---- end\n")
    }

    {
        // 打印出 sValue 中保存的元素,以及实际指向的值
        fmt.Println("---- sValue ---- start")
        fmt.Printf("%v, %v\n", sValue[0], *sValue[0])
        fmt.Printf("%v, %v\n", sValue[1], *sValue[1])
        fmt.Println("---- sValue ---- end\n")
    }
}
/* 输出
    ---- range ---- start  
    // 四列分别为:k的地址,k的值,v的地址,v的值
    key:0xc0000521f0,b, value:0xc0000180f0,2
    key:0xc0000521f0,a, value:0xc0000180f0,1
    ---- range ---- end

    ---- sKey ---- start 
    // 两列分别为:sKey[i]的地址, sKey[i]的值
    0xc0000044a0, b     
    0xc0000044b0, a
    ---- sKey ---- end

    ---- sValue ---- start
    // 三列分别为:sValue[i]的地址,sValue[i]的值,sValue[i]指针指向的值
    0xc000052230, 0xc0000180f0, 1
    0xc000052238, 0xc0000180f0, 1
    ---- sValue ---- end                       
*/
  • 在遍历的时候,输出的map里面的k,v的值都是正常的
  • 在遍历的时候,输出的range的k,v两个量的内存分别指向同一处,如k的内存地址,一直都是0xc0000521f0

在遍历的同时,分别保存了k,v的每次遍历的内容到一个slice中,不同点在于,k保存的是值,而v保存的是指针

  • 在输出 sKey 的时候,可以正确的输出结果。见: sKey 的输出
  • 在输出 sValue 的时候,发现两次的输出结果是一样的。(这里就是出错的地方)

因为我们将元素加入到 sValue 中的时候,做了v 的取地址操作。前面提到,在使用range遍历map的时候,v一直使用的同一块内存,因此对v取地址后,加入到 sValue 中的值,一直都是一个,因此 sValue输出了错误的结果。

同样地,如果在遍历的时候,对k进行取地址后,保存到变量里面,如 sKey2 := []*string{}里面,sKey2 的 输出也就出错了。

那么这里一个疑问就是,为什么对k直接加到到slice中,就不会有问题?

因为k在加入到 sKey 中的时候,保存的是k的副本的值,从 sKey 的输出的第一列可以看出,sKey 的两个元素的地址是不同的。而 v在取地址加入到 sValue中的时候,虽然也是 指针的副本,但是副本里面保存的值是v的指针的值,而这个值在遍历过程中一直是一个不会发生变化,因此出错


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:

查看原文:golang map遍历

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

11962 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传