什么是方法集?值接收者与指针接收者有哪些差异?
结论先行
方法集决定“一个类型实现了哪些方法、能匹配哪些接口”:类型 T 的方法集只包含值接收者方法,而 *T 的方法集同时包含值接收者方法与指针接收者方法;选哪种接收者会直接影响该类型能否满足接口、能否在不可寻址的值上调用方法。
要点
- 语法糖:值变量调用指针接收者方法时,编译器在“可寻址”前提下自动取地址(&v);字面量、map 元素、接口内的动态值等不可寻址对象不行。
- 方法集规则:T 的方法集 = { 值接收者方法 };*T 的方法集 = { 值接收者方法 ∪ 指针接收者方法 }。
- 接口匹配:var _ I = t 要求 I 需要的方法都在 T 的方法集里;若含指针接收者方法,编译失败并提示 *T 才满足 I。
- 语义差异:值接收者方法内修改的是副本,不影响原对象;指针接收者方法内修改会作用到原对象,适合“需要改状态”的类型。
- 一致性建议:同一类型尽量统一用值或统一用指针接收者,混用会让“到底谁实现了接口”的直觉混乱。
- 性能考虑:大 struct 用值接收者会整体拷贝,方法调用开销高,一般用指针接收者避免复制。
- nil 接收者:指针接收者方法可以被 nil 指针调用,但方法内访问字段会 panic,需要自行判空。
示例
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // 值接收者
func (c *Counter) Inc() { c.n++ } // 指针接收者
c := Counter{}
c.Inc() // 等价 (&c).Inc(),c 可寻址所以编译通过
var cp *Counter = &c
cp.Inc()
type Incable interface{ Inc() }
var _ Incable = &c // 编译通过
// var _ Incable = c // 编译错误:Counter 的方法集不含 Inc()
常见追问/记忆点
- 追问:为什么 map 元素、接口值不能直接调用指针接收者方法?答:它们不可寻址,编译器取不到 &elem。
- 追问:值接收者方法能被 *T 调用吗?答:能,*T 的方法集包含值接收者方法,自动解引用即可。
- 追问:nil 指针调用方法会怎样?答:方法本身能进,访问其字段或调用依赖字段的逻辑时会 panic。
- 记忆:指针接收者“能收也能发”(*T 继承值方法);值类型“只能发”(T 只有值方法)。