Ezreal 书架 Ezreal 书架
Home
  • 《Go程序员面试笔试宝典》
  • 《RabbitMQ 实战指南》
  • 《深入理解kafka》
  • MySQL45讲
  • 透视HTTP协议
  • 结构化数据的分布式存储系统
  • Raft 共识算法
Home
  • 《Go程序员面试笔试宝典》
  • 《RabbitMQ 实战指南》
  • 《深入理解kafka》
  • MySQL45讲
  • 透视HTTP协议
  • 结构化数据的分布式存储系统
  • Raft 共识算法
  • 逃逸分析

  • 延迟语句

  • 数据容器

  • 通道

  • 接口

    • Go 接口与 C++接口有何异同
    • Go 语言与“鸭子类型”的关系
    • iface 和 eface 的区别是什么
    • 值接收者和指针接收者的区别
      • 方法
      • 值接收者和指针接收者
      • 两者分别在何时使用
    • 如何用 interface 实现多态
    • 接口的动态类型和动态值是什么
    • 接口转换的原理是什么
    • 类型转换和断言的区别是什么
    • 如何让编译器自动检测类型是否实现了接口
  • unsafe

  • context

  • Go程序员面试笔试宝典
  • 接口
ezreal_rao
2023-05-31
目录

值接收者和指针接收者的区别

# 方法

方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,它就变成了方法。接收者可以是值接收者,也可以是指针接收者。

在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。

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

来看个例子:

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())
}
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
29
30
31
32
33

输出的结果是:

18
19
100
101
1
2
3
4

调用了 growUp 函数后,不管调用者是值类型还是指针类型,它的 Age 值都改变了。

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

不同类型调用者和接受者

# 值接收者和指针接收者

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

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

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

package main

import "fmt"

func main() {
	var c coder = &Gopher{"Go"}
	c.code()
	c.debug()
}

type coder interface {
	code()
	debug()
}

type Gopher struct {
	language string
}

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

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

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

上述代码里定义了一个接口 coder,接口定义了两个函数:

code()
debug()
1
2

接着定义了一个结构体 Gopher,它实现了两个方法,一个是值接收者,一个是指针接收者。 最后,在 main 函数里通过接口类型的变量调用了定义的两个函数。

运行结果如下:

I am coding Go language
I am debuging Go language
1
2

但是如果把 main 函数的第一条语句换一下:

func main() {
    var c coder = Gopher{"Go"}
    c.code()
    c.debug()
}
1
2
3
4
5

运行一下,出现报错:

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)
1
2

看出这两处代码的差别了吗?第一次是将 &Gopher 赋给了 coder;第二次则是将 Gopher 赋给了 coder。

第二次报错是说,Gopher 没有实现 coder。很明显了吧,因为 Gopher 类型并没有实现 debug 方法;表面上看,*Gopher 类型也没有实现 code 方法,但是因为 Gopher 类型实现了 code 方法,所以让 *Gopher 类型自动拥有了 code 方法。

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

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

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

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

# 两者分别在何时使用

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

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

1)方法能够修改接收者指向的值。
2)避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。

但是决定使用值接收者还是指针接收者,不是由该方法是否修改了调用者(也就是接收者), 而是应该基于该类型的本质。

如果类型具备 “原始的本质”,也就是说它的成员都是由 Go 语言里内置的原始类型,如字符串、整型值等构成, 那就定义值接收者类型的方法。 像内置的引用类型, 如 slice、map、 interface、channel,这些类型比较特殊,声明它们的时候,实际上是创建了一个 header,对于它们也是直接定义值接收者类型的方法。 这样, 调用函数时, 是直接复制了这些类型的 header, 而 header 本身就是为复制设计的。这种也应该是声明值接收者类型的方法。

如果类型具备非原始的本质,不能被安全地复制,这种类型总是应该被共享,那就定义指针接收者的方法。比如 Go 源码里的文件结构体(struct File)就不应该被复制,应该只有一份实体,就要定义指针接收者类型的方法。

#golang#interface
上次更新: 7/12/2024, 2:37:05 PM
iface 和 eface 的区别是什么
如何用 interface 实现多态

← iface 和 eface 的区别是什么 如何用 interface 实现多态→

最近更新
01
为什么我的MySQL会抖一下
07-15
02
HTTP 性能优化面面观
07-12
03
WebSocket:沙盒里的 TCP
07-12
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Ezreal Rao | CC BY-NC-SA 4.0
豫ICP备2023001810号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式