Golang中为什么map会创建一个新的副本?

Golang中为什么map会创建一个新的副本? 为什么 Tel.lookup["idx"].valueTel.m.value 的值不相同?我以为我在映射中保存的是引用,当我取出引用并更新它时,我得到的是变量的副本吗?

游乐场链接 https://goplay.space/#8HAHI9WG12-

package main

import (
    "fmt"
)

func main() {
    var Tel PkgVar = *Init()
    Tel.lookup["idx"].value = "NewValue"
    fmt.Println(Tel.lookup["idx"].value)              // 为什么这些值不相同?
    fmt.Println(Tel.m.value)                          // <-
}

func Init() *PkgVar {
    var p PkgVar
    p.m = metric{"Original_value"}
    p.lookup = map[string]*metric{
        "idx": &p.m,
    }
    return &p
}

type PkgVar struct {
    m      metric
    lookup map[string]*metric
}

type metric struct {
    value string
}

更多关于Golang中为什么map会创建一个新的副本?的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

请使用三个反引号来格式化您的代码,像这样:``` 你的代码在这里 ```。 这能让论坛中的代码更易于阅读。


通过 var Tel PkgVar = *Init() 这个赋值操作,您对值进行了解引用并创建了该值的副本。 现在,Tel.lookup["idx"] 仍然指向来自 Init() 函数的 &p.m,这并不等同于 Tel.m(那个局部变量)。

您可以将赋值改为 var Tel *PkgVar = Init() 来解决这个问题。

更多关于Golang中为什么map会创建一个新的副本?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,map存储的是值的副本,但当值是指针类型时,存储的是指针的副本(即内存地址的副本)。在你的代码中,lookup映射存储的是*metric指针,所以实际上存储的是指向p.m的内存地址。

问题出现在你的代码逻辑中,而不是map的行为上。让我分析一下:

package main

import (
    "fmt"
)

func main() {
    var Tel PkgVar = *Init()  // 这里发生了值拷贝!
    Tel.lookup["idx"].value = "NewValue"
    fmt.Println(Tel.lookup["idx"].value)  // 输出: NewValue
    fmt.Println(Tel.m.value)              // 输出: Original_value
}

func Init() *PkgVar {
    var p PkgVar
    p.m = metric{"Original_value"}
    p.lookup = map[string]*metric{
        "idx": &p.m,  // 这里存储的是局部变量p.m的地址
    }
    return &p
}

关键问题在于:

  1. Init()返回的是指向局部变量p的指针
  2. main()中的var Tel PkgVar = *Init()进行了值拷贝,创建了一个新的PkgVar实例
  3. 新的Tel.m是原p.m的副本,但lookup映射中的指针仍然指向原来的p.m(已被销毁)

验证这个问题的示例:

package main

import (
    "fmt"
)

func main() {
    // 方法1:直接使用指针,避免值拷贝
    Tel := Init()
    Tel.lookup["idx"].value = "NewValue"
    fmt.Println(Tel.lookup["idx"].value)  // 输出: NewValue
    fmt.Println(Tel.m.value)              // 输出: NewValue
    
    fmt.Println("\n--- 分割线 ---\n")
    
    // 方法2:展示值拷贝的问题
    var Tel2 PkgVar = *Init()
    fmt.Printf("Tel2.m 地址: %p\n", &Tel2.m)
    fmt.Printf("Tel2.lookup[\"idx\"] 指向的地址: %p\n", Tel2.lookup["idx"])
    fmt.Println("两个地址是否相同?", &Tel2.m == Tel2.lookup["idx"])
}

func Init() *PkgVar {
    var p PkgVar
    p.m = metric{"Original_value"}
    p.lookup = map[string]*metric{
        "idx": &p.m,
    }
    fmt.Printf("Init中 p.m 地址: %p\n", &p.m)
    return &p
}

type PkgVar struct {
    m      metric
    lookup map[string]*metric
}

type metric struct {
    value string
}

输出会是:

Init中 p.m 地址: 0x14000124020
NewValue
NewValue

--- 分割线 ---

Init中 p.m 地址: 0x14000124030
Tel2.m 地址: 0x14000124040
Tel2.lookup["idx"] 指向的地址: 0x14000124030
两个地址是否相同? false

解决方案:

// 方案1:使用指针
func main() {
    Tel := Init()  // 直接使用指针
    Tel.lookup["idx"].value = "NewValue"
    fmt.Println(Tel.lookup["idx"].value)  // NewValue
    fmt.Println(Tel.m.value)              // NewValue
}

// 方案2:在Init函数中正确初始化
func Init() *PkgVar {
    var p PkgVar
    p.m = metric{"Original_value"}
    p.lookup = map[string]*metric{
        "idx": &p.m,  // 指向结构体自身的m
    }
    return &p
}

// 方案3:避免值拷贝后重新建立指针关系
func Init() PkgVar {
    var p PkgVar
    p.m = metric{"Original_value"}
    p.lookup = map[string]*metric{
        "idx": &p.m,
    }
    return p  // 返回值,但map中的指针仍然有效
}

总结:map存储指针副本是正确的,问题在于你的值拷贝操作导致结构体被复制,但map中的指针仍然指向原始对象的地址。

回到顶部