Go中方法接收者是指针类型和值类型的根本区别

前言

方法接收者是指针类型和值类型的根本区别只需要明确这一点:它们的方法名是不一样的。

方法名

首先我们定义两个结构体类型:

1
2
3
4
type Man struct{
}
type Woman struct {
}

我们分别使用指针接收者和值接收者给他们添加一个Say()方法:

1
2
3
4
5
6
7
8
//此方法的全名为(*Man).Say()
//即只有指针类型*Man才有Say()方法
func (*Man) Say() {
}
//此方法的全名为(Woman).Say()
//只有值类型Woman才有Say()方法
func (Woman) Say() {
}

这里虽然它们都是Say()方法,但实际上方法名是不一样的,如果你使用指针接收者,方法的全名为(*Man).Say(), 如果是值类型,则全名Woman.Say()。严格的来说,对于前者,只能使用(*Man)类型来调用Say()方法,后者则是只能使用Woman类型来调用,因为值类型Man并没有Say()方法,同理指针类型*Woman也没有Say()方法。

编译器隐式转换

但是测试发现,Man.say()也能通过编译:

1
2
man := Man{} // man是个值类型
man.Say() // ok, 编译器将man隐式转换成了&Man

原因:go的编译器为我们做了一次隐式转换,即将man.say()转换成了(&man).say(),也就是对man做了取地址的操作。

同理,

1
2
Woman := &Woman{} //Woman为指针类型
Woman.Say() // ok, 编译器将指针类型Woman隐式转换成了Woman值类型

对于接口

对于接口类型,编译器并没有再主动为我们做类型转换

首先定义一个接口:

1
2
3
4
type Talk interface {
// 说话方法
Say()
}

首先我们承认Man和Woman类型都实现了这个接口。但其实,严格的说,因为Man的Say()方法是指针接收者,所以只是指针类型*Man实现了这个接口,值类型Man并没有实现这个接口。同理,因为Woman的say()方法是值类型,所以只是值类型Woman实现了这个接口,指针类型*Woman并没实现这个接口。

如果你把值类型的Man的变量赋值给接口Talk会报错:

1
2
3
4
5
6
7
8
9
10
11
12
//定义个接口变量
var talk Talk
//分别定义值类型Man1,指针类型Man2
Man1 := Man{}
Man2 := &Man{}
//
talk = Man2 //ok, *Man2类型有Say()方法
//
talk = Man1 //error,报错Man1类型没有Say()方法
//报错信息:
//Cannot use 'Man1' (type Man) as type Talk in assignment
//Type does not implement 'Talk' as 'Say' method has a pointer receiver

其他注意

实例代码:main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main
import "fmt"

type Person struct {
Name string
Age int
}

func (p *Person) show() {
fmt.Println( p.Name, p.Age)
}

func (p Person) modify1() {
p.Age = 38
}

func (p *Person) modify2() {
p.Age = 38
}

func main() {
a := Person{"xiao ming", 28}
a.show() //xiao ming 28
a.modify1()
a.show() //xiao ming 28
a.modify2()
a.show() //xiao ming 38
}

从输出结果我们看到,调用值类型的方法和调用指针类型的方法是不同的

当你需要修改a的时候要调用 func (p *Person) modify2() 指针类型的方法
如果你调用 func (p Person) modify1() 这样的值类型的方法是修改不成功的,因为调用时使用的是 a 的一个 copy