讲师中心 微信公众号
AI工具推荐 视频效率加速

Go Map中存储结构体:值类型与指针类型的选择与影响

雨静同学_2760

雨静同学_2760

发布时间:2025-07-16 14:04:24

|

607人浏览过

|

来源于php中文网

原创

go map中存储结构体:值类型与指针类型的选择与影响

本文深入探讨了Go语言中将结构体作为Map值类型时,使用map[int]struct(值类型)与map[int]*struct(指针类型)的主要区别。核心在于值类型存储的是结构体副本,而指针类型存储的是结构体的引用。这种差异直接影响结构体的可变性、内存管理以及在Map中对结构体成员进行操作的方式,特别是关于Map元素地址不可取的问题,以及如何根据业务需求选择合适的存储方式。

核心区别:值语义与指针语义

在Go语言中,map[int]vertex 和 map[int]*vertex 在存储结构体时,体现了值语义(Value Semantics)和指针语义(Pointer Semantics)的根本差异。

  • map[int]vertex (值类型): 当你向Map中添加一个 vertex 结构体时,Go会创建一个该结构体的完整副本并将其存储在Map内部。这意味着Map中存储的每个元素都是一个独立的 vertex 实例。后续对原始结构体(或Map中取出的副本)的修改,不会影响Map中存储的那个副本,反之亦然。

  • map[int]*vertex (指针类型):* 当你向Map中添加一个 `vertex` 指针时,Map中存储的不是结构体本身,而是指向该结构体在内存中位置的一个引用**。因此,通过这个指针,你可以直接访问并修改原始结构体,且所有持有该指针的地方都会看到这些修改。这实现了数据共享。

以下代码示例清晰地展示了这一区别:

package main

import "fmt"

type vertex struct {
    x, y int
}

func main() {
    a := make(map[int]vertex)    // 存储 vertex 值
    b := make(map[int]*vertex)   // 存储 *vertex 指针

    v := &vertex{0, 0} // 创建一个 vertex 实例的指针

    a[0] = *v // 将 v 指向的 vertex 值复制一份存入 map a
    b[0] = v  // 将 v 指针本身存入 map b

    // 此时,v 和 b[0] 指向同一个内存地址
    // a[0] 是 v 指向的 vertex 的一个独立副本

    v.x, v.y = 4, 4 // 修改原始 v 指向的结构体

    // 打印结果:
    // a[0].x, a[0].y 仍然是 0 0,因为它是一个副本,不受 v 修改的影响。
    // b[0].x, b[0].y 变为 4 4,因为它是一个指针,指向的内存地址与 v 相同。
    fmt.Println(a[0].x, a[0].y, b[0].x, b[0].y) // Output: 0 0 4 4
}

从输出可以看出,对原始 v 的修改只影响了 b[0],而 a[0] 保持不变,这正是值拷贝和引用传递的本质区别。

可变性与地址性问题

理解 map[int]vertex 与 map[int]*vertex 之间差异的关键在于Go语言中Map元素的地址性(Addressability)。在Go中,Map中的元素是不可寻址的(non-addressable)。这意味着你不能直接获取Map中某个值的内存地址,也就不能直接修改其内部字段。

当你尝试对 map[int]vertex 中的结构体成员进行直接修改时,例如 a[0].x = 3,编译器会报错 cannot assign to (a[0]).x。这是因为 a[0] 返回的是 vertex 结构体的一个副本,而不是其在Map中存储位置的引用。你不能直接修改这个副本,因为Map的设计不允许你直接修改其内部存储的值。如果允许这样做,当Map内部进行哈希表重排或扩容时,该副本的内存地址可能会改变,导致之前获取的地址失效,引发数据不一致或运行时错误。

相反,对于 map[int]*vertex,b[0] 返回的是一个 *vertex 指针。这个指针本身是可寻址的,更重要的是,它指向的 vertex 结构体是可寻址的。因此,你可以通过 b[0].x = 3 的方式,通过指针解引用来修改指针所指向的原始结构体的成员。

Go语言(Golang)1.26.0
Go语言(Golang)1.26.0

Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。

下载

以下扩展代码进一步演示了这一点:

package main

import "fmt"

type vertex struct {
    x, y int
}

func main() {
    a := make(map[int]vertex)
    b := make(map[int]*vertex)

    v := &vertex{0, 0}
    a[0] = *v
    b[0] = v

    v.x, v.y = 4, 4
    fmt.Println("Initial state (v modified):", a[0].x, a[0].y, b[0].x, b[0].y)
    // Output: Initial state (v modified): 0 0 4 4

    // a[0].x = 3 // 编译错误:cannot assign to (a[0]).x
    // a[0].y = 3 // 编译错误:cannot assign to (a[0]).y

    // 对于 map[int]vertex,若要修改,必须取出副本,修改后重新存入
    tempVertex := a[0] // 取出副本
    tempVertex.x = 3
    tempVertex.y = 3
    a[0] = tempVertex // 存回 Map

    b[0].x = 3 // 通过指针直接修改
    b[0].y = 3 // 通过指针直接修改
    fmt.Println("After direct/re-assign modification:", a[0].x, a[0].y, b[0].x, b[0].y)
    // Output: After direct/re-assign modification: 3 3 3 3

    // 再次验证通过局部变量修改的影响
    u1 := a[0] // u1 是 a[0] 的一个副本
    u1.x = 2
    u1.y = 2
    // a[0] 不受 u1 修改的影响,因为 u1 只是一个副本
    fmt.Println("After u1 modification:", a[0].x, a[0].y) // Output: After u1 modification: 3 3

    u2 := b[0] // u2 是 b[0] 指针的一个副本,它指向的仍然是原来的结构体
    u2.x = 2
    u2.y = 2
    // b[0] 受 u2 修改的影响,因为它们指向同一个结构体
    fmt.Println("After u2 modification:", b[0].x, b[0].y) // Output: After u2 modification: 2 2

    fmt.Println("Final state:", a[0].x, a[0].y, b[0].x, b[0].y)
    // Output: Final state: 3 3 2 2
}

上述代码的输出与问题中提供的输出一致,清晰地展示了值类型与指针类型在Map中操作时的行为差异。

选择建议与注意事项

在决定使用 map[int]vertex 还是 map[int]*vertex 时,应考虑以下因素:

  1. 可变性需求:

    • 如果你需要在Map中直接修改结构体的成员,那么必须使用 map[int]*vertex。
    • 如果你只需要读取结构体的值,或者每次修改都意味着创建一个新的结构体实例并替换旧的,那么 map[int]vertex 是可行的。但请记住,修改 map[int]vertex 中的元素需要取出副本、修改、再重新赋值回Map。
  2. 内存与性能:

    • 对于小型且不经常修改的结构体,map[int]vertex 可能更简单直观,因为它避免了指针的额外开销和垃圾回收器的复杂性。每次访问都会得到一个副本,这可能在某些场景下增加内存复制的开销。
    • 对于大型或频繁修改的结构体,map[int]*vertex 通常是更优的选择。它避免了在每次赋值或传递时进行大结构体的内存拷贝,从而节省内存和提高性能。但这也意味着需要管理指针的生命周期,并可能引入 nil 指针的风险。
  3. 并发安全:

    • 无论选择哪种,如果Map在多个goroutine之间共享,都必须考虑并发安全问题。Map本身不是并发安全的,需要使用 sync.RWMutex 或 sync.Map 来保护并发访问。
    • 对于 map[int]*vertex,即使Map本身是并发安全的,指针指向的结构体也可能被多个goroutine同时修改,这需要额外的同步机制(如 sync.Mutex 内嵌在结构体中,或使用原子操作)来保护结构体内部成员。
  4. 值拷贝与引用传递的语义:

    • map[int]vertex 强调值拷贝语义,每个Map条目都是独立的。
    • map[int]*vertex 强调引用传递语义,多个Map条目可以指向同一个底层结构体。这使得共享数据和实现更复杂的数据结构成为可能,但也增加了数据依赖性和潜在的副作用。

总结

总而言之,map[int]vertex 和 map[int]*vertex 之间的选择取决于你的具体需求。如果你需要直接修改Map中存储的结构体实例,或者结构体较大且需要避免频繁拷贝,那么 map[int]*vertex 是首选。如果你处理的是小型、相对静态的结构体,并且可以接受取出、修改、再存入的模式,或者希望每个Map条目都是一个完全独立的副本,那么 map[int]vertex 可能更合适。理解Go语言中Map元素的地址性限制是做出正确选择的关键。

热门AI工具

更多
PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

UpDream
UpDream Hot

一款AI视频创作工具,主要用于哔哩哔哩推出的自研AI视频创作工具,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

小橡皮AI

一款AI工具,主要用于小橡皮一键去AI味,将机器味重的内容改得更有活人感,防止因为 AI 内容被平台打上 AI 标签,以及限流,适合需要提升相关任务效率的用户。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

5214

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

5371

2025.07.04

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

7139

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3405

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

4468

2025.08.29

C++中int的含义
C++中int的含义

本专题整合了C++中int相关内容,阅读专题下面的文章了解更多详细内容。

3025

2025.08.29

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

379

2026.04.23

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2781

2023.12.01

DBeaver怎么导入数据
DBeaver怎么导入数据

DBeaver数据导入专题,整理Excel、CSV、SQL、TXT等常见文件的数据导入方法,涵盖目标数据库选择、数据表创建、字段映射、数据类型设置及导入参数配置等操作,帮助用户将本地文件中的数据写入数据库,并了解批量导入、重复数据处理和导入失败等常见问题的解决方法。

0

2026.11.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn