方法接收者用 T 还是 T 取决于是否需修改原值:T 可修改字段且避免大结构体拷贝,T 仅适用于只读场景;接口实现时若含 T 方法则仅 T 实例可赋值;嵌入字段的提升方法调用也受接收者类型约束。

方法接收者用 *T 还是 T?看要不要改原值
Go 里方法能不能修改调用者的字段,只取决于接收者类型是不是指针。值接收者 T 拿到的是副本,改了也白改;指针接收者 *T 才能真正写回原结构体。
常见错误现象:func (u User) SetName(n string) { u.name = n } 调完 u.name 还是老样子——因为 u 是副本。
- 如果方法要修改字段、或字段本身很大(比如含切片、map、大数组),必须用
*T - 如果只是读取字段、计算返回值,
T更轻量,避免解引用开销 - 同一个类型上混用两种接收者会让人困惑,建议统一:要么全用
*T,要么全用T(除非有明确理由)
接口实现时,*T 和 T 的实现能力不等价
接口变量存储的是「能调用该接口方法的类型」。而 Go 规定:T 类型的值能调用 T 和 *T 方法,但 *T 类型的值只能调用 *T 方法(不能自动转成 T 再调)。
典型报错:cannot use t (type *T) as type interface{ M() } in argument to f: *T does not implement interface{ M() } (M method has pointer receiver)
立即学习“go语言免费学习笔记(深入)”;
- 只要接口中任一方法用了
*T接收者,那只有*T实例能赋值给该接口 - 如果你导出类型并希望别人能方便地用值来满足接口,就别在接口方法里用指针接收者
- 标准库如
fmt.Stringer明确要求String() string用值接收者,就是为兼容性考虑
嵌入字段时,接收者类型决定“提升方法”的可用性
当结构体 A 嵌入 B,A 会“获得”B 的方法。但这些方法是否可用,仍取决于你调用时用的是 A 还是 *A,以及 B 的方法接收者类型。
例如:type B struct{}; func (b *B) Do() {},再 type A struct{ B },那么:
-
a := A{}; a.Do()→ 编译失败:没有Do方法可用(a是值,但B.Do需要*B) -
ap := &A{}; ap.Do()→ 成功:Go 能从&A提取出&B,匹配*B接收者 - 如果
B.Do是func (b B) Do(),那a.Do()和ap.Do()都行
性能和逃逸分析:小结构体用 T 可能更省
值接收者会拷贝整个结构体。对 struct{ int, int } 这种小对象,拷贝比解引用还快;但对含 []byte 或大数组的结构体,拷贝开销明显,且可能触发堆分配(逃逸)。
验证方式:go build -gcflags="-m" main.go 看是否出现 ... escapes to heap。
- 字段总大小 ≤ 几个机器字长(如 16–24 字节),优先考虑
T - 含 slice/map/chan/func/interface 的结构体,几乎总是该用
*T - 不确定时,用
go tool compile -S看汇编里有没有大量MOVQ拷贝指令
最麻烦的不是选错一次,而是同一类型的方法接收者不一致——调用方永远得猜哪个要取地址、哪个不能取。这点比性能影响更实际。










