必须用指针接收者的情况包括:需修改字段、结构体较大避免拷贝、实现接口(仅指针方法集包含指针接收者方法)、含sync.Mutex或需nil初始化;值接收者适用于小结构体纯只读操作。

必须用指针接收者的情况,编译器不会报错,但逻辑会静默失效——比如 Inc() 看似调用了,count 却没变;而该用值接收者却用了指针,可能引入 nil panic 或破坏接口兼容性。选错不是语法错误,而是语义陷阱。
方法要改字段?只能用 *T
Go 所有参数(包括方法接收者)都是值传递。值接收者 func (t T) SetX(x int) 操作的是 t 的完整副本,改了也白改。
-
func (c *Counter) Inc() { c.count++ }→ 原实例count真的加 1 -
func (c Counter) Inc() { c.count++ }→ 副本改了,原始c不动 - 哪怕只改一个
int字段,只要需持久化,就必须用指针接收者
结构体太大?默认用 *T 避免拷贝
拷贝开销取决于结构体实际大小,不是“有没有指针字段”。一个含 [1024]byte 或 map[string]int 的 struct,值接收者每次调用都复制数百字节甚至 MB 级内存。
-
type Config struct { Data []byte; Options map[string]bool }→ 必须用func (c *Config) Validate() -
type Point struct { X, Y int }→func (p Point) Distance(q Point) float64完全 OK,拷贝成本可忽略 - 切片、map、chan 本身是指针包装类型,但它们所在的 struct 整体仍会被完整复制
实现接口时,T 和 *T 方法集不等价
接口能否赋值,取决于类型的方法集是否包含接口所有方法。而 Go 规则很明确:T 的方法集只含值接收者方法;*T 的方法集包含值 + 指针接收者方法。
立即学习“go语言免费学习笔记(深入)”;
-
func (u *User) Save() error→ 只有*User能赋给Saver接口,User{}直接编译失败 - 混用接收者:若已有
func (u *User) Save(),再加func (u User) Name() string,会导致User和*User行为割裂 - 标准库惯例(如
bytes.Buffer、net.Conn)全部用指针接收者,不是巧合
含 sync.Mutex 或需要延迟初始化?必须用 *T
锁字段一旦被复制,就变成两个独立互斥锁,完全失去同步意义;nil 指针在方法内做判空初始化,也是指针接收者的常见模式。
-
type Cache struct { mu sync.RWMutex; data map[string]string }→func (c *Cache) Get(k string) string是唯一安全写法 -
func (c *Cache) init() { if c.data == nil { c.data = make(map[string]string) } }→ 值接收者无法完成此初始化 - 小结构体且纯只读(如
type RGBA struct{r,g,b,a uint8})用值接收者反而更安全,避免意外解引用nil
真正难的不是记住规则,而是在定义第一个方法时就决定风格:改不改状态?会不会变大?别人会不会拿它实现接口?这三个问题问完,接收者类型基本就定了。后续加方法时,别为了“这个方法只读”就切回值接收者——一致性比单个方法的轻量更重要。


















