【问题标题】:Modifying a map value in golang - with and without pointers - unexpected behavior [closed]在 golang 中修改映射值 - 有和没有指针 - 意外行为 [关闭]
【发布时间】: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 := data{ID: "one", Value: "oneval"}
    myData["one"] = foo
    foo = data{ID: "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 := data{ID: "one", Value: "oneval"}
    myData["one"] = &foo
    foo = data{ID: "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 := data{ID: "one", Value: "oneval"}
        myData["one"] = &foo
    }
    // Really, anything to make 'foo' go out of scope
    foo := data{ID: "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)
    }
}

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

【问题讨论】:

  • 您应该对指针的概念有点熟悉。这确实是所有基本和预期的行为。

标签: go pointers data-structures struct


【解决方案1】:

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

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

foo := data{ID: "one", Value: "oneval"}

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

myData["one"] = &foo

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

foo = data{ID: "two", Value: "twoval"}

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

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

foo := data{ID: "one", Value: "oneval"}
myData["one"] = &foo
foo2 := data{ID: "two", Value: "twoval"}
myData["two"] = &foo2

或者更简单:

myData["one"] = &data{ID: "one", Value: "oneval"}
myData["two"] = &data{ID: "two", Value: "twoval"}

【讨论】:

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

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

foo := data{ID: "one", Value: "oneval"}
myData["one"] = &foo
foo = data{ID: "two", Value: "twoval"}
myData["two"] = &foo

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-27
    • 2015-08-01
    • 2014-02-20
    • 1970-01-01
    • 2017-04-26
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    相关资源
    最近更新 更多