什么是方法集?值接收者与指针接收者有哪些差异?

结论先行

方法集决定“一个类型实现了哪些方法、能匹配哪些接口”:类型 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 只有值方法)。
笔记加载中…