Go的值接收者和指针接收者
Go 中没有类(class),类似的特性是结构(struct)。正如类可以有方法那样,结构也可以有方法。结构的方法有两种形式:值接收者(value receiver)和指针接收者(pointer receiver)。本文将讲一下它们的区别,以及我的个人理解。
区别一:能否修改原实例
值接收者拿到的是实例的副本,在方法内部修改这个副本,不会影响原实例。指针接收者拿到的是实例的地址,因此可以修改原实例。
1 | |
区别二:调用规则
当运行 x.M() 时,Go 会先在 x 的方法集中寻找 M。在 Go 中,方法集的规则是:类型的方法集只包含值接收者,类型的指针的方法集同时包含值接收者和指针接收者。
另外,还有一个语法糖:如果 x 是可寻址(addressable)的,并且 M 在 &x 的方法集中,x.M() 会自动转换成 (&x).M()。
1 | |
不管是 u 还是 p,都可以调用值接收者和指针接收者。u 可寻址,所以可以通过语法糖调用 PointerMethod。p 是类型的指针,它的方法集本身就包含了两个方法。
当直接写 T{} 和 &T{} 时,情况就会略有不同。T{} 是临时值,不可寻址,所以无法使用语法糖调用 PointerMethod。&T{} 也是临时值,但它的方法集本身就包含了两个方法。
区别三:接口规则
Go 判断一个类型是否满足一个接口时,只看它的方法集,并没有调用时的可寻址的语法糖。
1 | |
个人理解
在 C++ 中,类方法可以被标注为常量(const),相当于保证“这个方法不会修改当前实例”。常量方法不仅不能直接修改普通成员,还不能返回成员的引用或指针(常量版本除外)——因为引用和指针可以允许外部调用者绕过常量方法间接修改实例。
Go 的指针接收者可以用类似的方式理解。指针接收者相当于申请“这个方法需要修改当前实例”。考虑到在 Go 中,值大概率是副本——不论是把结构作为入参传给方法,或赋值给另一个变量,都会产生副本。在副本上修改不会影响原实例,也无法满足指针接收者的申请。所以只有类型的指针的方法集才包含指针接收者方法。