本文详解在 go 中如何正确使用 reflect 包,从 interface{} 参数中获取结构体字段的指针(*int 等),避免 `unsafe.pointer` 误用,并提供可直接运行的三种实践方式:类型断言解包指针、直接设值、以及适配 scan 类函数的接口传递。
在 Go 反射中,reflect.Value.Addr() 返回的是一个 新的 reflect.Value,它表示原字段的地址(即指向该字段的指针值),而非 unsafe.Pointer。而 .Pointer() 方法返回的是底层 uintptr,不能直接解引用(*ptr 会导致编译错误或 panic),这是初学者最常见的误区。正确路径是:先用 .Addr().Interface() 将反射值转为 interface{},再通过类型断言获得真实指针。
以下是一个完整、安全、可运行的示例:
package main
import (
"fmt"
"reflect"
)
type Robot struct {
Id int
}
// ✅ 方式一:获取 *int 指针并修改(适用于需传入 &val 的场景,如 database/sql.Scan)
func fWithPointer(i interface{}) {
v := reflect.ValueOf(i).Elem().FieldByName("Id")
ptr := v.Addr().Interface().(*int) // 安全转换:interface{} → *int
*ptr = 100
}
// ✅ 方式二:直接设值(更简洁、推荐用于纯修改场景)
func fDirectSet(i interface{}) {
v := reflect.ValueOf(i).Elem().FieldByName("Id")
v.SetInt(100) // 自动处理可寻址
性,无需指针转换
}
// ✅ 方式三:适配 Scan 类函数(如 sql.Rows.Scan)——直接传递 interface{}
func fForScan(i interface{}) {
v := reflect.ValueOf(i).Elem().FieldByName("Id")
// 假设 scan 是一个接受 interface{} 的函数(如 db.Scan)
scan(v.Addr().Interface()) // 传入 *int,类型为 interface{},完全兼容
}
// 模拟 Scan 函数(仅作演示)
func scan(dest interface{}) {
if p, ok := dest.(*int); ok {
*p = 200
}
}
func main() {
robot := &Robot{}
fWithPointer(robot)
fmt.Println("After fWithPointer:", robot.Id) // 输出: 100
robot2 := &Robot{}
fDirectSet(robot2)
fmt.Println("After fDirectSet:", robot2.Id) // 输出: 100
robot3 := &Robot{}
fForScan(robot3)
fmt.Println("After fForScan:", robot3.Id) // 输出: 200
}⚠️ 关键注意事项:
总结:Go 反射的核心原则是「用 Interface() 回到类型安全世界,再用类型断言落地」;避免过早落入 uintptr 和 unsafe 层级。掌握 Addr() → Interface() → type assertion 这一链条,即可稳健实现动态字段指针访问与修改。