关于映射、字符串指针和闭包的问题

当我尝试转换为时,我发现我的代码存在错误(我map[string]string正在map[string]*string使用的 API 需要转换)。

问题

在我的第一个方法中,我尝试遍历源映射中的每个条目,aMap并将每个条目的字符串值转换为字符串指针,并将此指针值分配给目标映射,bMapaMap. 我最初预计保留的取消引用值bMapaMap同一键下的值相同。但是,事实证明,在同一键下, 的延迟值bMap不共享相同的值。aMap

我的问题是:

  1. 为什么会出现这个问题?

  2. 我能够使用第二种方法解决该问题,首先定义一个接收字符串并返回指针的函数,这样我就能够正确返回延迟值以共享与aMap同一键下相同的值。为什么这种方法有效?

  3. 另外,只是好奇,为什么&(*v)Go 中有语法错误?我还尝试声明一个虚拟变量val := *v,并将其分配给bMapusingbMap[key] = &val但无济于事。错误说invalid indirect of v (type string)

源代码

https://play.golang.org/p/ZG7XS2vJx0y


func main() {

    aMap := make(map[string]string)

    aMap["foo"] = "bar"

    aMap["bar"] = "baz"

    aMap["baz"] = "foo"


    bMap := make(map[string]*string)

    for k, v := range(aMap){

        bMap[k] = &v

    }


    // first method

    fmt.Println("1st method, map[string]*string, bMap") 

    for k, v := range(bMap){

        fmt.Printf("bMap[%s] = %s / %s, aMap[%s] = %s\n",

        k, *bMap[k], *v, k, aMap[k])

    }


    pString := func(v string) *string{ return &v }

    for k, v := range(aMap){

        bMap[k] = pString(v)

    }


    // second method

    fmt.Println("2nd method, map[string]*string, bMap")

    for k, v := range(bMap){

        fmt.Printf("bMap[%s] = %s / %s, aMap[%s] = %s\n",

        k, *bMap[k], *v, k, aMap[k])

    }


    // expected results

    fmt.Println("Expected result: map[string]string, cMap")

    cMap := make(map[string]string)

    for k, v := range(aMap){

        cMap[k] = v

    }

    for k, v := range(cMap){

        fmt.Printf("cMap[%s] = %s / %s, aMap[%s] = %s\n",

        k, cMap[k], v, k, aMap[k])

    }

}

输出

1st method, map[string]*string, bMap

bMap[baz] = foo / foo, aMap[baz] = foo

bMap[foo] = foo / foo, aMap[foo] = bar

bMap[bar] = foo / foo, aMap[bar] = baz

2nd method, map[string]*string, bMap

bMap[baz] = foo / foo, aMap[baz] = foo

bMap[foo] = bar / bar, aMap[foo] = bar

bMap[bar] = baz / baz, aMap[bar] = baz

Expected result: map[string]string, cMap

cMap[baz] = foo / foo, aMap[baz] = foo

cMap[foo] = bar / bar, aMap[foo] = bar

cMap[bar] = baz / baz, aMap[bar] = baz

太感谢了。


蓝山帝景
浏览 187回答 2
2回答

牧羊人nacy

方法 1 中的问题是您没有获取字符串的地址,而是获取了它所在变量的地址。在 Go 中,&v返回变量的地址v。当你有这样的循环时:for k, v := range aMap {    ...}您在循环开始时声明的变量k和v在整个循环中使用的变量是相同的。它们只是在每次迭代中被分配了不同的值。在该循环中,&v总是计算出相同的值: 的地址v。这就是为什么您的所有地图条目都出现在"foo":"foo"中的最后一个值v,并且它仍然存在。您可以通过更改字符串值来查看此行为。所有的地图键都会改变:*bMap["foo"] = "quux"fmt.Println(*bMap["bar"]) // prints "quux"方法 2 有效,因为函数的每次调用都有自己的局部变量。Go 保证,如果您从函数返回局部变量的地址,该变量将被分配在堆上,只要需要它就可以使用它。所以你的辅助函数告诉 Go 分配一个新string变量,将传入的字符串复制到它,然后返回它的地址。这是另一种可行的方法:dMap := make(map[string]*string)for k, v := range(aMap){    dMap[k] = new(string)    *dMap[k] = v}这会分配一个新的字符串变量,将其地址保存在映射中,然后将 v 复制到其中。如果您要经常这样做,辅助函数可能是最好的。您尝试的代码如下:v := "foo"val := *vbMap[key] = &val不起作用,因为您说“v 是一个字符串;现在将字符串指向的值存储在 val 中”,但该字符串不是指针。

哔哔one

问题中的第一种方法使用v所有键的单个循环变量的地址。您看到的值是最后一个设置为 的值v。通过为每次迭代声明一个新变量并获取该变量的地址来修复。bMap := make(map[string]*string)for k, v := range aMap {    v := v // declare new variable v initialized with value from outer v    bMap[k] = &v}问题中的第二种方法还为循环中的每次迭代声明了一个新变量。新变量是函数参数。这个答案显示了解决这个问题的惯用方法。请参阅Go 常见问题解答:作为 goroutine 运行的闭包会发生什么?在闭包和 goroutine 的上下文中讨论相同的问题。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go