环境
Windows 10
golang 1.17
前言
接收者是值类型的方法(例如下面代码中的valueAddAge
方法),下文统称为值方法
接收者是指针类型的方法(例如下面代码中的ptrAddAge
方法),下文统称为指针方法
代码
package main
import "fmt"
type Person struct {
age int
}
// 值方法
func (p Person) valueAddAge() {
p.age++
}
// 指针方法
func (p *Person) ptrAddAge() {
p.age++
}
func main() {
// p1是值类型,调用值方法
p1 := Person{age: 18}
p1.valueAddAge()
fmt.Println(p1.age) // 输出18,没有改变
// p2是值类型,调用指针方法
p2 := Person{age: 18}
p2.ptrAddAge()
fmt.Println(p2.age) // 输出19,有改变
// p3是指针类型,调用值方法
p3 := &Person{age: 18}
p3.valueAddAge()
fmt.Println(p3.age) // 输出18,没有改变
// p4是指针类型,调用指针方法
p4 := &Person{age: 18}
p4.ptrAddAge()
fmt.Println(p4.age) // 输出19,有改变
}
1.调用方法时,都是把调用者(调用者是指上面代码中的p1
、p2
、p3
、p4
变量)复制一份传递给方法。
2.如果调用者的类型,和接收者的类型不相同,会隐式的把调用者类型转换成接收者的类型。
例如上述代码中的p2
和p3
例子就属于类型不一致的。p2.ptrAddAge()
会转换成(&p2).ptrAddAge()
,对p2
取地址,就得到了一个指针,这样调用者的类型就和接收者一致了,然后把指针复制一份传给方法;
注:仅当
p2
是可寻址(addressable,或者说左值)的时候,才能对其取地址,否则会报错的
同理,p3.valueAddAge()
会转换成(*p3).valueAddAge()
,对p3
取值,得到了一个值类型的变量,再把变量复制一份传给方法。
如果调用者是指针类型,我们知道,即使将指针复制一份,它指向的原始数据还是不变的,因此在方法中对调用者的数据进行修改,是会影响到调用者的;
但是如果调用者是值类型的,值类型的变量被复制了一份,那新的变量就不是原来的那个变量了,因此在方法里对新变量做修改,是不会影响到原调用者的。
结论
值方法 和 指针方法 的区别,就是看方法里的操作是否会影响到调用者。值方法不会影响,指针方法会影响。
其它:与interface接口组合使用
在上一个场景中,无论调用者
和接收者
是什么类型,调用者都能成功调用到方法。但是,当与 interface
接口组合使用时,情况又会变得不一样,比如以下代码:
package main
import "fmt"
// Human接口
type Human interface {
getAge() int
}
// Person结构体
type Person struct {
age int
}
// 实现Human接口的getAge方法,接收者是指针类型
func (rec *Person) getAge() int {
return rec.age
}
func main() {
// 创建一个Person对象,值类型
p1 := Person{age: 18}
// 把p1赋值给一个类型为Human的接口变量
var ivar Human = p1 // 这一行会报编译错误
}
上述代码中,我们将结构体变量p1
赋值给接口类型变量ivar
时,编译器会判断p1
变量的方法集里是否实现了接口Human
中定义的所有方法,如果没有全部实现,就会报错。
什么是方法集?简单来说就是:
- 如果
p1
是值类型,那它的方法集就是它的所有值方法(接收者为值类型的方法) - 如果
p1
是指针类型,那它的方法集就是它的所有值方法 + 指针方法(接收者为指针类型的方法)
很明显,p1
是值类型,而它只有一个指针方法,也就是说,它的方法集是空的,没有实现Human
接口中定义的方法,所以编译器报错了。