Tips

Go for range

2022-05-24  本文已影响0人  Sun东辉
type student struct {
    Name string
    Age  int
}

func three() int {
    m := make(map[string]*student)
    stus := []student{
        {Name: "zhou", Age: 24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }

    for _, stu := range stus {
        m[stu.Name] = &stu
    }

    for k, v := range m {
        println(k, "=>", v.Name) 
        // zhou => wang 
        // li => wang 
        // wang => wang
    }
}

这里的输出为什么不是?

zhou => zhou 
li => li
wang => wang

这里的原因在于使用 for range 遍历的时候,k, v 使用的同一块内存。

为什么?直接查看源码

func walkRange(nrange *ir.RangeStmt) ir.Node {
    ...
    switch t.Kind() {
            case types.TMAP:
                // order.stmt allocated the iterator for us.
                // we only use a once, so no copy needed.
                ha := a

                hit := nrange.Prealloc
                th := hit.Type()
                // depends on layout of iterator struct.
                // See cmd/compile/internal/reflectdata/reflect.go:MapIterType
                keysym := th.Field(0).Sym // 定义了 key 的地址
                elemsym := th.Field(1).Sym // ditto 定义了 value 的地址

                fn := typecheck.LookupRuntime("mapiterinit")

                fn = typecheck.SubstArgTypes(fn, t.Key(), t.Elem(), th)
                init = append(init, mkcallstmt1(fn, reflectdata.TypePtr(t), ha, typecheck.NodAddr(hit)))
                nfor.Cond = ir.NewBinaryExpr(base.Pos, ir.ONE, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym), typecheck.NodNil())

                fn = typecheck.LookupRuntime("mapiternext")
                fn = typecheck.SubstArgTypes(fn, th)
                nfor.Post = mkcallstmt1(fn, typecheck.NodAddr(hit))

                key := ir.NewStarExpr(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, keysym))
                if v1 == nil {
                    body = nil
                } else if v2 == nil {
                    body = []ir.Node{ir.NewAssignStmt(base.Pos, v1, key)}
                } else {
                    elem := ir.NewStarExpr(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, hit, elemsym))
                    a := ir.NewAssignListStmt(base.Pos, ir.OAS2, []ir.Node{v1, v2}, []ir.Node{key, elem})
                    body = []ir.Node{a}
                }
                ...
}

由此可以发现,key 和 value 在进入 range 时,地址被重新指定了。

除了遍历时的地址外,还有一点需要注意,map 遍历的顺序是无序的。源码如下

func makemap(t *maptype, hint int, h *hmap) *hmap {
    mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
    if overflow || mem > maxAlloc {
        hint = 0
    }

    // initialize Hmap
    if h == nil {
        h = new(hmap)
    }
    h.hash0 = fastrand() // 随机种子
    ...
}

即 map 在运行时的会使用随机种子,因此,上面程序中得到的 wang 是由随机种子决定的。

上一篇 下一篇

猜你喜欢

热点阅读