golang 值接收者和指针接收者
代码示例
先看个代码:
package main
import (
"fmt"
"testing"
)
func TestValRecv(t *testing.T) {
var a animal = dog{"五红犬"}
a.eat()
a.sleep()
var b animal = &dog{"五黑犬"}
b.eat()
b.sleep()
}
func TestPtrRecv(t *testing.T) {
var a animal = &cat{"蓝猫"}
a.eat()
a.sleep()
var b animal = cat{"胖橘"} // sleep only a ptr recv,出错
b.eat()
b.sleep()
}
type animal interface {
eat()
sleep()
}
type dog struct {
name string
}
func (d dog) eat() {
fmt.Printf("%s eat...\n", d.name)
}
func (d dog) sleep() {
fmt.Printf("%s sleep...\n", d.name)
}
type cat struct {
name string
}
func (d cat) eat() {
fmt.Printf("%s eat...\n", d.name)
}
func (d *cat) sleep() {
fmt.Printf("%s sleep...\n", d.name)
}
先说结论:
- 值接收者,默认实现了值接收和指针接收方法,源对象的一个复制或者拷贝
- 指针接收,只实现指针接收,重点是修改原对象
所以上面 cat 一个是 eat() 值接收方法,实现了二者,而 sleep() 指针接收,只实现了指针接收,未完全实现接口 animal 的两个方法。
如果不涉及接口,二者都可以,但涉及到接口涉及具体实现问题。
对于接口
结论:
实现了接收者是值类型的接口方法,相当于自动实现了接收者是指针类型的方法;
实现了接收者是指针类型的接口方法,不会自动生成对应接收者是值类型的方法。
简单的解释
接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响。
所以,当实现了一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原
本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。
两者分别在何时使用
如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。
使用指针作为方法的接收者的理由:
方法能够修改接收者指向的值。避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。
使用值接收者还是指针接收者,不是由该方法是否修改了调用者(也就是接收者)来决定,而是应该基于该类型的本质。
如果类型具备“原始的本质”,也就是说它的成员都是由 Go 语言里内置的原始类型,如字符串,整型值等,那就定义值接收者类型的方法。像内置的引用类型,如 slice,map,interface,channel,这些类型比较特殊,声明他们的时候,实际上是创建了一个 header, 对于他们也是直接定义值接收者类型的方法。这样,调用函数时,是直接 copy 了这些类型的 header,而 header 本身就是为复制设计的。
如果类型具备非原始的本质,不能被安全地复制,这种类型总是应该被共享,那就定义指针接收者的方法。比如 go 源码里的文件结构体(struct File)就不应该被复制,应该只有一份实体。
参考: