GoLang之值接收者和指针接收者的区别(2)

方法

值接收者指针接收者
值接收者指针接收者指针接收者值接收者

也就是说,不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。

来看个例子:

package main

import "fmt"

type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func (p *Person) growUp() {
	p.age += 1
}

func main() {
	// qcrao 是值类型
	qcrao := Person{age: 18}

	// 值类型 调用接收者也是值类型的方法
	fmt.Println(qcrao.howOld())

	// 值类型 调用接收者是指针类型的方法
	qcrao.growUp()
	fmt.Println(qcrao.howOld())

	// ----------------------

	// stefno 是指针类型
	stefno := &Person{age: 100}

	// 指针类型 调用接收者是值类型的方法
	fmt.Println(stefno.howOld())

	// 指针类型 调用接收者也是指针类型的方法
	stefno.growUp()
	fmt.Println(stefno.howOld())
}

上例子的输出结果是:

18
19
100
101
growUpAge

实际上,当类型和方法的接收者类型不同时,其实是编译器在背后做了一些工作,用一个表格来呈现:

qcrao.growUp()(&qcrao).growUp()stefno.howOld()(*stefno).howOld()

值接收者和指针接收者

前面说过,不管接收者类型是值类型还是指针类型,都可以通过值类型或指针类型调用,这里面实际上通过语法糖起作用的。

先说结论:实现了接收者是值类型的方法,相当于自动实现了接收者是指针类型的方法;而实现了接收者是指针类型的方法,不会自动生成对应接收者是值类型的方法。

来看一个例子,就会完全明白:

package main

import "fmt"

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

func (p Gopher) code() {
	fmt.Printf("I am coding %s language\n", p.language)
}

func (p *Gopher) debug() {
	fmt.Printf("I am debuging %s language\n", p.language)
}

func main() {
	var c coder = &Gopher{"Go"}
	c.code()
	c.debug()
}
coder
code()
debug()
Gophermain
I am coding Go language
I am debuging Go language
main
func main() {
	var c coder = Gopher{"Go"}
	c.code()
	c.debug()
}

运行一下,报错:

src/main.go:23:6: cannot use Gopher literal (type Gopher) as type coder in assignment:
	Gopher does not implement coder (debug method has pointer receiver)
&GophercoderGophercoder
GophercoderGopherdebug*GophercodeGophercode*Gophercode

当然,上面的说法有一个简单的解释:接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响。

所以,当实现了一个接收者是值类型的方法,就可以自动生成一个接收者是对应指针类型的方法,因为两者都不会影响接收者。但是,当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者。

最后,只要记住下面这点就可以了:

如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法。

两者分别在何时使用

如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身。

使用指针作为方法的接收者的理由:

1.方法能够修改接收者指向的值。

2.避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。

本质
headerheaderheader
实体

参考资料

【飞雪无情 Go实战笔记】https://www.flysnow.org/2017/04/03/go-in-action-go-interface.html

【何时使用指针接收者】http://ironxu.com/711

【理解Go Interface】http://lanlingzi.cn/post/technical/2016/0803_go_interface/

【Go语言实战 类型的本置】 图书《Go In Action》

package main

import "fmt"

type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func main() {
	a := Person{14}
	fmt.Println(a.howOld()) //14
}



package main

import "fmt"

type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func main() {
	a := &Person{14}
	fmt.Println(a.howOld()) //14
}

package main

import "fmt"

type Person struct {
	age int
}

func (p *Person) howOld() int {
	return p.age
}

func main() {
	a := &Person{14}
	fmt.Println(a.howOld()) //14
}

package main

import "fmt"

type Person struct {
	age int
}

func (p *Person) howOld() int {
	return p.age
}

func main() {
	a := Person{14}
	fmt.Println(a.howOld()) //14
}

package main

import "fmt"

type acm interface {
	howOld() int
}
type Person struct {
	age int
}

func (p *Person) howOld() int {
	return p.age
}

func main() {
	var a acm = Person{14}
	fmt.Println(a.howOld()) //编译出错
}

package main

import "fmt"

type acm interface {
	howOld() int
}
type Person struct {
	age int
}

func (p *Person) howOld() int {
	return p.age
}

func main() {
	var a acm = &Person{14}
	fmt.Println(a.howOld()) //14
}

package main

import "fmt"

type acm interface {
	howOld() int
}
type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func main() {
	var a acm = &Person{14}
	fmt.Println(a.howOld()) //14
}

package main

import "fmt"

type acm interface {
	howOld() int
}
type Person struct {
	age int
}

func (p Person) howOld() int {
	return p.age
}

func main() {
	var a acm = Person{14}
	fmt.Println(a.howOld()) //14
}