必须用指针的场景包括:修改原值、调用写入型函数、结构体含可变类型、实现指针接收者接口、字段需表达“未配置”语义;传参和接收者优先用指针以确保一致性与安全性。

什么时候必须用指针:改不了原值就等于没改
Go 里所有参数都是值传递,func modify(s MyStruct) 中的 s 是副本,改它毫无意义。只有传 *MyStruct,函数才能真正触达原始内存。
- 方法要改字段?接收者必须是
*T,否则赋值只作用于副本 - 调用
json.Unmarshal、binary.Read等写入型函数?参数必须是指针,否则解码内容无处落脚 - 结构体含
sync.Mutex、bytes.Buffer这类可变类型?必须用指针,值拷贝会破坏内部状态一致性 - 实现接口时,若接口方法定义在指针接收者上(比如
io.Reader.Read),你传值类型变量就无法满足该接口
结构体字段该用 *T 还是 T:看它能不能是空的
嵌套字段是否允许为 nil,才是决定用指针还是值的核心依据,性能反而是次要的。
- 想表达“未配置”“可选”“暂不启用”?用
*Settings,nil就是明确的语义信号 - 字段必须存在、总有默认含义?用
Settings,零值(如空字符串、0)就是合法初始态 -
json反序列化时,*string能区分"name":null和"name":"";而string字段两者都变成"" - 别为“省点内存”盲目指针化小结构体——
Point {X, Y int}用值类型更干净,也避免解引用 panic
函数参数传值还是传指针:先看大小,再看意图
编译器对“大”的提示很实在:如果函数栈帧警告 large stack frame,基本说明结构体 > 8 字节,该考虑指针了。
- 基础类型(
int、string、bool)和小结构体(≤2 个字段,且不含切片/map)——直接传值,清晰又高效 - 结构体含切片、map、大数组或字段超过 3 个?优先传
*T,避免每次调用都复制几百字节 - 并发场景下传值更安全:多个 goroutine 拿到的是独立副本,不会因共享指针引发数据竞争
- 传指针前务必确认非空——
if cfg != nil && cfg.Timeout > 0,漏判nil就是 runtime panic
方法接收者统一用指针:少一个 bug,多一分确定性
哪怕某个方法只读,只要结构体其他方法用了指针接收者,你就该全用 *T。混用会导致接口实现断裂和调用歧义。
立即学习“go语言免费学习笔记(深入)”;
-
func (u User) GetName() string和func (u *User) SetName(n string)共存?那User{}值不能调用SetName,但能调用GetName;而&User{}两者都能调——这种不一致极易埋雷 - 标准库几乎全用指针接收者处理可变结构体(
*bytes.Buffer、*http.Client),不是巧合,是经过验证的稳定性选择 - 不确定时,先写
func (u *User) DoSomething();等真发现某个方法纯只读且结构体极小,再评估是否值得改成值接收者
nil 不是性能优化技巧,它是 API 设计里的一个明确词义。


















