在 golang 中修改映射值 - 有和没有指针 - 意外行为 [关闭]

Posted

技术标签:

【中文标题】在 golang 中修改映射值 - 有和没有指针 - 意外行为 [关闭]【英文标题】:Modifying a map value in golang - with and without pointers - unexpected behavior [closed] 【发布时间】:2021-02-08 02:00:39 【问题描述】:

我是 golang 的新手,所以我正在尝试从其他语言(例如 Java / javascript)中调整我的想法并将其应用到 golang。在大多数情况下,这非常简单。然而,当涉及到遍历地图和修改值时,我有点难过。

考虑以下示例程序:

package main

import "fmt"

type data struct 
    ID    string
    Value string


func main() 

    myData := make(map[string]data)

    foo := dataID: "one", Value: "oneval"
    myData["one"] = foo
    foo = dataID: "two", Value: "twoval"
    myData["two"] = foo

    for _, v := range myData 
        fmt.Println(v.Value)
        v.Value = "kenny"
    
    for _, v := range myData 
        fmt.Println(v.Value)
    

在大多数语言中,我希望输出是:

oneval
twoval
kenny
kenny

当然不是。它是:

oneval
twoval
oneval
twoval

感谢这个 SO 答案 (How to update map values in Go),解决方案是将元素“重新分配”回地图中。哎呀,不过还好。

另一种选择是创建一个指向结构的指针映射,但这最终导致了一个相关的悖论。考虑这个更新的程序:

package main

import "fmt"

type data struct 
    ID    string
    Value string


func main() 
    // Now it's a map of struct pointers...
    myData := make(map[string]*data)

    foo := dataID: "one", Value: "oneval"
    myData["one"] = &foo
    foo = dataID: "two", Value: "twoval"
    myData["two"] = &foo

    for _, v := range myData 
        fmt.Println(v.Value)
        v.Value = "kenny"
    
    for _, v := range myData 
        fmt.Println(v.Value)
    

我希望输出是:

oneval
twoval
kenny
kenny

原来是这样的:

twoval
kenny
kenny
kenny

因为当变量foo被重新赋值时,映射中的相同值被重新赋值。什么????

因此,让它按预期工作的唯一方法是这样做:

package main

import "fmt"

type data struct 
    ID    string
    Value string


func main() 
    // Now it's a map of struct pointers...
    myData := make(map[string]*data)

    
        foo := dataID: "one", Value: "oneval"
        myData["one"] = &foo
    
    // Really, anything to make 'foo' go out of scope
    foo := dataID: "two", Value: "twoval"
    myData["two"] = &foo

    for _, v := range myData 
        fmt.Println(v.Value)
        v.Value = "kenny"
    
    for _, v := range myData 
        fmt.Println(v.Value)
    

显然,必须有更好的方法。所以我问你们——*** 社区的聪明人——到底发生了什么?

【问题讨论】:

您应该对指针的概念有点熟悉。这确实是所有基本和预期的行为。 【参考方案1】:

你声明了一个值,并存储了两个指向同一个值的指针。

以下声明 foo 并将文字 ID:"one",Value:"oneval" 分配给 is:

foo := dataID: "one", Value: "oneval"

这会在地图中存储指向foo 的指针。

myData["one"] = &foo

这会用新值ID:"two", Value:"twoval" 覆盖foo 的内容:

foo = dataID: "two", Value: "twoval"

此时,myData["one"] 指向包含ID:"two",Value:"twoval"foo

您需要存储指向两个不同内存位置的指针:

foo := dataID: "one", Value: "oneval"
myData["one"] = &foo
foo2 := dataID: "two", Value: "twoval"
myData["two"] = &foo2

或者更简单:

myData["one"] = &dataID: "one", Value: "oneval"
myData["two"] = &dataID: "two", Value: "twoval"

【讨论】:

嗯...关于覆盖 foo 的内容,这是一个有趣的观点。我认为它会创建一个全新的结构,并更新 foo 以指向新分配的结构。看来您是正确的:新结构被复制到 existing foo 结构中。感谢您的回答 - 非常感谢! 如果foo 被声明为指针,情况就会如此。 是的,我重写了代码 - 感谢你 - 使用指针,现在一切都按预期工作。再次感谢!【参考方案2】:

通过分配给变量,您不会更改其在内存中的地址。你只改变它的值。

foo := dataID: "one", Value: "oneval"
myData["one"] = &foo
foo = dataID: "two", Value: "twoval"
myData["two"] = &foo

在这里您正在更改变量foo 的值。它的地址在整个程序中保持不变。因此,当您将地址 foo 分配给 myData["one"] 并随后分配给 myData["two"] 时,实际上您为它们分配了相同的值。

fmt.Println(myData["one"] == myData["two"]) //true

【讨论】:

以上是关于在 golang 中修改映射值 - 有和没有指针 - 意外行为 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

golang中指针,new和make函数作用

golang指针修改值

用指针作为参数传值是否更节省内存?(c/c++/golang)

golang中定义的某个interface作为函数的入参时

Golang入门到项目实战 | golang指针

golang golang在嵌套映射中查找值