Go中该用指针当且仅当需修改原始值、传大结构体(>64字节)或实现接口;其余情况优先传值,更安全清晰且性能不差。

Go 里该不该用指针,不是看“能不能”,而是看“改不改”和“大不大”——改原始值、传大结构体、实现接口这三类场景必须用 *T;其余多数情况,直接传 T 更安全、更清晰、性能也不差。
什么时候必须用指针参数:改、反序列化、接口实现
函数签名里出现 *T,通常只因三个硬性理由:
- 需要修改调用方的原始变量(比如
reset(u *User)把u.Age设为 0) - 调用
json.Unmarshal、binary.Read这类写入型 API,它们的参数类型强制要求*T,传T直接编译失败 - 结构体某个方法用了指针接收者(如
func (u *User) Save()),那么只有*User类型能实现对应接口;若混用值接收者,会导致部分调用静默拷贝、部分调用才生效,行为割裂
结构体方法接收者选值还是指针:看是否要改 + 看大小 + 看一致性
结构体方法接收者类型不是性能微调选项,而是语义契约:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 只要有一个方法需要修改字段(比如
SetAge()、DeductStock()),整个结构体所有方法都该统一用*T接收者——否则User{}.Save()编译不过,而User{}.GetName()却能跑,逻辑断裂 - 结构体字段总大小 ≤ 16 字节(比如两个
int64或一个string+ 一个int),值接收器开销极小,且避免了 nil 检查和逃逸到堆的风险 - 结构体含切片、映射、通道等引用类型字段时,值接收器仍只复制头信息(24 字节),实际数据不会被拷贝,此时用值接收器也完全可行
传大结构体时用指针,但别误判“大”的标准
所谓“大”,不是主观感觉,而是看拷贝成本是否显著影响性能或内存。几十 MB 的临床报告结构体当然要传 *PatientReport;但一个含 5 个字段的 Order 结构体(总长不到 40 字节),传值比传指针还快——因为指针要解引用、可能触发逃逸、还多一次 nil 判断。
- 实测建议:结构体总大小超过 64 字节时,才开始考虑指针传递
- 注意陷阱:切片本身是小结构体(3 字段,24 字节),即使它底层数组很大,传
[]byte值也没问题;真正要警惕的是像[1024]int这种大数组字段 - 如果结构体里嵌了大数组或大 map,又不想暴露指针语义,可把大字段单独抽成新类型并加指针方法,主结构体继续用值传递
最容易被忽略的坑:nil 检查和局部变量地址返回
这两类错误不会在编译时报错,但上线后要么 panic,要么读到垃圾值:
- 所有公开接收
*T参数的函数,第一行该是if t == nil { return }或明确返回错误;别指望调用方永远传非 nil -
func getPtr() *int { x := 42; return &x }是典型错误——x在栈上分配,函数返回后内存失效,后续解引用行为未定义 - 用
new(T)或&T{}创建堆上对象没问题,但别对局部变量取地址后传出;如果真需要动态生命周期,用make或显式new
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










