将 map 的值指针添加到 slice 的行为

behavior of map's value pointer being added to slice

func TestMapValuePointer2(t *testing.T) {
    fmt.Println("Test Map Value Pointer 2")
    m := map[string]int{"rsc": 3711, "r": 2138, "gri": 1908, "adg": 912}
    n := len(m)
    array := make([]*int, n)
    i := 0
    for _, v := range m {
        array[i] = &v
        fmt.Printf("Add to array: %d\n", v)
        i++
    }
    for _, k := range array {
        fmt.Printf("Value: %d\n", *k)
    }
}

输出不是:

价值:912
价值:3711
价值:2138
价值:1908

相反,输出可能是这样的:

价值:912
价值:912
价值:912
价值:912

谁能解释一下为什么结果像上面这样?

引用自this doc

[...] each iteration of the loop uses the same instance of the variable v, so each closure shares that single variable [...]

换句话说,循环变量 v 在所有迭代中重复使用,因此您将同一个指针分配给所有切片元素。

循环中声明的变量不会像那样被回收,所以这将如您所料工作:

for _, v := range m {
    vv := v
    array[i] = &vv
    fmt.Printf("Add to array: %d\n", v)
    i++
}

顺便说一句,您还没有解释为什么要使用 *int 作为值的类型。如果您只使用 int 值,所有这些都会更简单。

这里的问题是在循环期间创建的变量 v 实际上是我们在地图中拥有的值的副本。与 v 相关的内存在循环开始时分配一次,稍后会重新用于其他值,因为此处指向的最后一个值是 912,在这种情况下,您会在数组中看到 912。

对此的一个简单证明是:https://play.golang.org/p/K0yAbEIf3G

解决此问题的一个方法是将地图值更改为 *int 指针而不是值,稍后我们可以取消引用它们以获取实际值:

package main

import "fmt"

func main() {
    fmt.Println("Test Map Value Pointer 2")
    a, b, c, d := 3711, 2138, 1908, 912
    m := map[string]*int{"rsc": &a, "r": &b, "gri": &c, "adg": &d}
    n := len(m)
    array := make([]*int, n)
    i := 0
    for _, v := range m {
        array[i] = v
        fmt.Printf("Add to array: %d\n", *v)
        i++
    }
    for _, k := range array {
        fmt.Printf("Value: %d\n", *k)
    }
}

游乐场link:https://play.golang.org/p/TpkXlCElLx