新闻中心
Go语言中Map遍历的指针陷阱:理解循环变量的地址行为与解决方案

本文深入探讨了go语言中遍历map时,对循环变量直接取地址可能导致的常见陷阱。当在`for...range`循环中尝试获取`res`(值类型)的地址并存储时,由于`res`是循环变量的副本且其内存地址在迭代中被重用,最终会导致存储的多个指针都指向同一个内存位置,从而产生意料之外的重复地址问题。文章提供了两种有效的解决方案:一是将map的值类型改为指针类型,二是显式创建循环变量的副本再取地址,确保每个存储的指针都指向独立的内存对象。
Go语言中for...range循环变量的特性
在Go语言中,for...range循环是遍历切片、数组、字符串、Map和通道的常用方式。当遍历Map时,range会返回键和值的副本。这意味着在for key, value := range myMap这样的结构中,value是一个新的变量,它在每次迭代时都会被赋予Map中对应元素的副本。
理解这一点至关重要:value(或本例中的res)在循环的每次迭代中,其内存地址通常是固定的,只是它所存储的“内容”会随着Map中不同元素的赋值而更新。
问题重现:Map遍历中的指针陷阱
考虑以下场景,我们有一个存储Result结构体(值类型)的Map,并尝试在遍历时获取每个Result的地址并存储到一个切片中:
package main
import "fmt"
// Result 结构体用于示例
type Result struct {
Port int
}
func main() {
m := make(map[string]Result)
m["server1"] = Result{Port: 6379}
m["server2"] = Result{Port: 6380}
// 初始化一个存储 *Result 指针的切片
r := make([]*Result, len(m))
i := 0
for _, res := range m { // res 是 Result 类型的值副本
// 打印当前迭代的 res 值和 res 变量的内存地址
fmt.Printf("Iteration %d: res value = %+v, address of res variable = %p\n", i, res, &res)
// 将循环变量 res 的地址存储到切片中
r[i] = &res
i++
}
fmt.Println("\n--- 遍历结束后切片 r 的内容 ---")
// 打印切片 r 中存储的指针及其指向的值
for idx, ptr := range r {
// 注意:*ptr 会显示循环结束后 res 的最终值
fmt.Printf("r[%d] points to address %p, value = %+v\n", idx, ptr, *ptr)
}
}运行上述代码,你可能会得到类似如下的输出(具体地址值可能不同):
Iteration 0: res value = {Port:6379}, address of res variable = 0xc0000100a0
Iteration 1: res value = {Port:6380}, address of res variable = 0xc0000100a0
--- 遍历结束后切片 r 的内容 ---
r[0] points to address 0xc0000100a0, value = {Port:6380}
r[1] points to address 0xc0000100a0, value = {Port:6380}从输出中可以清楚地看到问题:
- 在每次迭代中,res的值确实是Map中不同的Result结构体({Port:6379}和{Port:6380})。
- 然而,res变量本身的内存地址(address of res variable)在两次迭代中是相同的(例如0xc0000100a0)。
- 最终,切片r中存储的两个指针都指向了这个相同的内存地址。由于这个地址最终被{Port:6380}覆盖,所以r中的所有指针都指向了Map中最后一个元素的值。
根本原因分析
这个问题的核心在于for...range循环中res变量的生命周期和内存分配机制。
- res是循环作用域内的一个局部变量。
- 在每次迭代开始时,Go运行时会将Map中当前元素的值副本赋值给res。
- res变量的内存空间在整个循环过程中是复用的,它的地址不会改变。
- 当我们执行r[i] = &res时,我们存储的是这个循环变量res的地址,而不是Map中原始元素的地址,也不是每次迭代中res所持有的值的独立副本的地址。
- 当循环结束后,res变量仍然存在,并持有Map中最后一个元素的值。因此,所有指向&res的指针都将指向这个最终的值。
解决方案一:将Map的值类型定义为指针
最直接且推荐的解决方案是,如果你的业务逻辑允许,将Map的值类型本身定义为指针类型。这样,Map存储的就已经是Result结构体的指针,for...range循环变量res也将是一个指针。直接存储res即可,因为它已经指向了独立的Result实例。
GoEnhance
全能AI视频制作平台:通过GoEnhance AI让视频创作变得比以往任何时候都更简单。
347
查看详情
package main
import "fmt"
type Result struct {
Port int
}
func main() {
// Map存储 Result 结构体的指针
m := make(map[string]*Result)
m["server1"] = &Result{Port: 6379} // 存储指针
m["server
2"] = &Result{Port: 6380} // 存储指针
r := make([]*Result, len(m))
i := 0
for _, res := range m { // res 此时已经是 *Result 类型(一个指针)
// 打印当前迭代的 res 指针的值和 res 变量的内存地址,以及 res 指向的值
fmt.Printf("Iteration %d: res value = %+v, address of res variable = %p, value pointed to by res = %p\n", i, *res, &res, res)
r[i] = res // 直接存储 res (它本身就是一个指向 Result 结构体的指针)
i++
}
fmt.Println("\n--- 遍历结束后切片 r 的内容 ---")
for idx, ptr := range r {
fmt.Printf("r[%d] points to address %p, value = %+v\n", idx, ptr, *ptr)
}
}输出示例:
Iteration 0: res value = {Port:6379}, address of res variable = 0xc0000100a0, value pointed to by res = 0xc0000120e0
Iteration 1: res value = {Port:6380}, address of res variable = 0xc0000100a0, value pointed to by res = 0xc0000120f0
--- 遍历结束后切片 r 的内容 ---
r[0] points to address 0xc0000120e0, value = {Port:6379}
r[1] points to address 0xc0000120f0, value = {Port:6380}可以看到,此时r中存储的是不同的指针地址(0xc0000120e0和0xc0000120f0),它们分别指向了Map中原始的Result结构体实例,解决了重复地址的问题。
解决方案二:显式创建循环变量的副本
如果Map的值类型必须是值类型(例如出于内存、性能或语义上的考虑),那么我们可以在循环内部显式地创建一个res的副本,然后获取这个副本的地址。这样,每次迭代都会创建一个新的副本,并获得其独立的内存地址。
package main
import "fmt"
type Result struct {
Port int
}
func main() {
// Map存储 Result 结构体的值类型
m := make(map[string]Result)
m["server1"] = Result{Port: 6379}
m["server2"] = Result{Port: 6380}
r := make([]*Result, len(m))
i := 0
for _, res := range m { // res 仍然是 Result 类型的值副本
fmt.Printf("Iteration %d: res value = %+v, address of res variable = %p\n", i, res, &res)
// 显式创建 res 的副本,并获取副本的地址
temp := res // 创建一个 res 的新副本
r[i] = &temp // 存储副本的地址
i++
}
fmt.Println("\n--- 遍历结束后切片 r 的内容 ---")
for idx, ptr := range r {
fmt.Printf("r[%d] points to address %p, value = %+v\n", idx, ptr, *ptr)
}
}输出示例:
Iteration 0: res value = {Port:6379}, address of res variable = 0xc0000100a0
Iteration 1: res value = {Port:6380}, address of res variable = 0xc0000100a0
--- 遍历结束后切片 r 的内容 ---
r[0] points to address 0xc0000120e0, value = {Port:6379}
r[1] points to address 0xc0000120f0, value = {Port:6380}此方案同样有效。temp变量在每次迭代中都是一个新的局部变量,拥有独立的内存地址。因此,&temp会产生不同的指针,指向不同的Result副本。
选择合适的方案与注意事项
- *Map存储指针类型 (`map[string]Result`)**:
- 优点:代码简洁,直接存储res即可。Map中存储的是引用,修改Result实例会影响所有指向它的指针。对于大型结构体,可以减少内存复制开销。
- 缺点:需要手动管理Result实例的创建(例如使用`&Result{
以上就是Go语言中Map遍历的指针陷阱:理解循环变量的地址行为与解决方案的详细内容,更多请关注其它相关文章!
# 两种
# 海外烤鸡网站推广怎么做
# 企业短视频推广营销策划
# 刘德华个人网站建设游戏
# 宜昌网站建设策略优化
# 抖音新媒体营销推广方案
# 苏州推广营销哪个好
# 模板建站能seo
# seo和竞价取舍
# 四川农村电商网站建设
# 佛山外贸网站推广费用
# 两次
# go
# 一是
# 多个
# 创建一个
# 是一个
# 的是
# 结束后
# 迭代
# 遍历
# 作用域
# ai
# go语言
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
如何在离线环境中使用Composer_Composer离线安装依赖包的技巧与策略
Safari浏览器输入栏卡顿如何解决 Safari搜索建议与缓存清理
优化LangChain文档加载与ChromaDB集成:解决多文档处理与分块问题
小米Civi 4录制视频过暗_小米Civi 4亮度优化
EMS快递官网app_中国邮政速递物流手机客户端
拼多多赚钱渠道_拼多多收益来源
包子漫画官方网站在线链接-包子漫画在线阅读平台主页地址
夸克浏览器桌面版同步不了书签怎么处理 夸克浏览器跨设备同步异常解决方案
Python多线程中正确使用sigwait处理SIGALRM信号
初次安装JDK时环境变量如何正确配置_J*A_HOME与PATH设置规则讲解
J*aScript中向JSON对象添加新属性的正确姿势
解决 MongoDB 聚合查询中对象数组 _id 匹配问题
使用CSS更改登录屏幕输入框中PNG图标颜色的策略与局限性
Win11怎么查看显卡显存 Win11显示适配器属性及专用视频内存查询
最新韩小圈网页版登录入口_官网在线观看官方链接
J*a TimerTask中HashMap意外清空的深层原因与解决方案
J*a实现学校排课程序_面向对象结构化项目示例
C++如何操作注册表_Windows平台下C++读写注册表的API函数详解
《噬血代码2》新预告片发布 展示游戏剧情
小红书网页版入口链接分享 小红书官网直接进
c++中为什么推荐使用using替代typedef_c++现代化类型别名
苹果手机指南针不准怎么校准 传感器校准方法详解【建议收藏】
J*aScript实现动态背景色下的文本与按钮颜色自适应调整
微博网页版官方账号登录 微博网页版内容浏览使用指南
Golang如何使用context实现超时取消_Golang context超时取消模式实践
Composer的 "licenses" 命令如何帮助你遵守开源协议_检查项目依赖的许可证合规性
Surface怎么安装系统 微软Surface Pro U盘重装win11教程
AO3官方在线访问地址 Archive of Our Own最新镜像合集
如何仅使用CSS更改登录界面背景图像图标的颜色
内存疯狂猛猛涨价:主板销量直接腰斩!
印象笔记怎样用批量导出备知识库_印象笔记用批量导出备知识库【备份方法】
163邮箱注册官网 免费申请163个人邮箱
海棠电脑版入口_通过电脑访问海棠官网阅读
CSS Flexbox与媒体查询:实现响应式布局中元素的并排与堆叠
如何解决电商平台定制报价请求的“黑洞”问题,SprykerQuoteRequest模块助你提升客户体验与销售效率
整合Supabase认证与Django模型:跨模式迁移的解决方案
文本文档写html代码怎么运行_文本文档html代码运行步骤【教程】
Golang如何优化内存分配与垃圾回收_Golang内存管理与GC优化实践
C++如何实现一个智能指针_手动实现C++ shared_ptr的引用计数功能
可靠CSGO开箱平台解析 CSGO开箱网合集
如何在网页中实现特定地点的随机图片展示
CSS布局:解决全屏元素100%尺寸与外边距导致的页面溢出问题
在J*a中如何开发在线活动报名与管理系统_活动报名管理项目实战解析
Django表单提交验证失败后保持字段值不刷新
Sublime Text怎么设置垂直标尺_Sublime配置Rulers规范代码长度
c++如何使用TBB库进行任务并行_c++ Intel线程构建模块
必由学官网入口 必由学教师登录入口
Win11蓝牙耳机断连怎么解决 Win11蓝牙设置重新配对与驱动更新【技巧】
excel怎么制作工资条 excel快速生成工资条的方法
163邮箱官方主页登录 直达网易邮箱登录核心页面


2025-11-30
浏览次数:次
返回列表
2"] = &Result{Port: 6380} // 存储指针
r := make([]*Result, len(m))
i := 0
for _, res := range m { // res 此时已经是 *Result 类型(一个指针)
// 打印当前迭代的 res 指针的值和 res 变量的内存地址,以及 res 指向的值
fmt.Printf("Iteration %d: res value = %+v, address of res variable = %p, value pointed to by res = %p\n", i, *res, &res, res)
r[i] = res // 直接存储 res (它本身就是一个指向 Result 结构体的指针)
i++
}
fmt.Println("\n--- 遍历结束后切片 r 的内容 ---")
for idx, ptr := range r {
fmt.Printf("r[%d] points to address %p, value = %+v\n", idx, ptr, *ptr)
}
}