如何正确使用反射为结构体字段生成扫描地址以避免指针地址重复

老涛同学_6805

老涛同学_6805

2026-10-15

192人浏览

原创

如何正确使用反射为结构体字段生成扫描地址以避免指针地址重复

在 Go 中使用 structs.Names() 获取结构体字段名并动态构建 sql.Rows.Scan() 所需的指针切片时,若错误地对循环变量取地址(如 &val),会导致所有指针指向同一内存地址,从而造成数据覆盖。本文详解问题成因与正确实现方式。

在 go 中使用 `structs.names()` 获取结构体字段名并动态构建 `sql.rows.scan()` 所需的指针切片时,若错误地对循环变量取地址(如 `&val`),会导致所有指针指向同一内存地址,从而造成数据覆盖。本文详解问题成因与正确实现方式。

该问题的根本原因在于 Go 的 for range 循环中,循环变量 val 是单个复用的栈变量,每次迭代仅更新其值,而非创建新变量。因此 &val 始终返回同一个地址:

for i, val := range userLoginKeys {
    keys[i] = &val // ❌ 错误:所有 &val 指向同一内存位置
}

运行时输出 [0xc4201ca2c0 0xc4201ca2c0] 正是这一行为的直接证据——两个指针地址完全相同,导致 row.Scan() 将查询结果(如 username 和 password)连续写入同一块内存,最终二者值被后者覆盖(本例中均为哈希密码)。

✅ 正确做法是:对切片元素本身取地址,确保每个指针指向独立的字符串值:

userLoginKeys := structs.Names(UserLogin{})
keys := make([]interface{}, len(userLoginKeys))
for i := range userLoginKeys {
    keys[i] = &userLoginKeys[i] // ✅ 正确:取切片第 i 个元素的地址
}

但需注意:structs.Names() 返回的是 []string,其元素是不可寻址的临时值(Go 规范限制对 slice 元素取地址需 slice 本身可寻址)。上述写法在某些 Go 版本中可能触发编译错误。

? 更健壮、推荐的解决方案是预先声明字段变量,再取其地址:

func Login() {
    row := sql.QueryRow("SELECT username, password FROM users WHERE username=?", "golang")

    var username, password string
    // 直接传入字段地址(清晰、安全、无反射开销)
    err := row.Scan(&username, &password)
    if err != nil {
        log.Fatal(err)
    }

    // 映射到结构体实例
    user := UserLogin{
        Username: username,
        Password: password,
    }
    fmt.Printf("%+v\n", user)
}

若必须动态扫描(例如字段数未知或需泛型适配),应使用 reflect 构建结构体字段指针,而非依赖 structs.Names():

func scanIntoStruct(row *sql.Row, dest interface{}) error {
    v := reflect.ValueOf(dest).Elem()
    if v.Kind() != reflect.Struct {
        return fmt.Errorf("dest must be a pointer to struct")
    }

    // 构建字段指针切片
    values := make([]interface{}, v.NumField())
    for i := 0; i <p>? <strong>关键总结</strong>:  </p>
  • for range 中的 val 是复用变量,&val 永远返回同一地址;
  • 切片元素 slice[i] 仅在 slice 本身可寻址时才可取地址(如局部变量 slice);
  • 生产环境优先采用显式变量 + Scan(&a, &b),简洁安全;
  • 动态场景务必通过 reflect.Value.Field(i).Addr() 获取真实字段地址,避免逻辑陷阱。
PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

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

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

2025.06.09

4554

18

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

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

2025.07.04

4711

25

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

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

2026.04.23

339

31

Go中Type关键字的用法
Go中Type关键字的用法

Go中Type关键字的用法有定义新的类型别名或者创建新的结构体类型。本专题为大家提供Go相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.06

2769

5

go怎么实现链表
go怎么实现链表

go通过定义一个节点结构体、定义一个链表结构体、定义一些方法来操作链表、实现一个方法来删除链表中的一个节点和实现一个方法来打印链表中的所有节点的方法实现链表。

2023.09.25

5487

6

go语言编程软件有哪些
go语言编程软件有哪些

go语言编程软件有Go编译器、Go开发环境、Go包管理器、Go测试框架、Go文档生成器、Go代码质量工具和Go性能分析工具等。本专题为大家提供go语言相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.13

682

5

0基础如何学go语言
0基础如何学go语言

0基础学习Go语言需要分阶段进行,从基础知识到实践项目,逐步深入。php中文网给大家带来了go语言相关的教程以及文章,欢迎大家前来学习。

2023.10.26

7245

20

Go语言实现运算符重载有哪些方法
Go语言实现运算符重载有哪些方法

Go语言不支持运算符重载,但可以通过一些方法来模拟运算符重载的效果。使用函数重载来模拟运算符重载,可以为不同的类型定义不同的函数,以实现类似运算符重载的效果,通过函数重载,可以为不同的类型实现不同的操作。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

2576

5

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.02.23

2824

5

热门下载

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

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.7万人学习