golang方法

go语言方法使用

Posted by 张嘉伟 on 2019-11-21        本文总阅读量

Golang中的方法

Golang允许用户定义类型,比如常见的结构体就属于用户定义的结构类型。方法可以给用户定义的类型添加新的行为,方法其实是一种特殊的函数,与函数的区别在于声明的时候在关键字func和函数名之间增加了一个参数。为了更好的理解方法的概念,我们先来看一段代码:

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
34
35
36
37
package main

import "fmt"

// 定义一个user类型
type user struct {
name string
email string
}

// 使用值接收者实现一个方法
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n", u.name, u.email)
}

// 使用指针接收者实现一个方法
func (u *user) changeEmail(email string) {
u.email = email
}

func main() {
// user类型的值可以用来调用使用值接收者声明的方法
tracy := user{"Tracy", "tracy.qq.com"}
tracy.notify()

// 指向user类型值的指针也可以调用使用值接收者声明的方法
kobe := &user{"Kobe", "kobe.qq.com"}
kobe.notify()

// user类型的值可以用来调用使用指针接收者声明的方法
tracy.changeEmail("guoguo.qq.com")
tracy.notify()

// 指向user类型值的指针也可以调用使用指针接收者声明的方法
kobe.changeEmail("xiaobudian.qq.com")
kobe.notify()
}

代码输出结果如下:

1
2
3
4
Sending User Email To Tracy<tracy.qq.com>
Sending User Email To Kobe<kobe.qq.com>
Sending User Email To Tracy<guoguo.qq.com>
Sending User Email To Kobe<xiaobudian.qq.com>

值接收者和指针接收者

除了两者声明方法时候形式不同外,本质的区别在哪里呢?下面我们通过分析main函数中四段调用来理解两者的含义:

1、类型的值调用使用值接收者声明的方法

1
2
3
// user类型的值可以用来调用使用值接收者声明的方法
tracy := user{"Tracy", "tracy.qq.com"}
tracy.notify()

这里我们声明并初始化了一个user类型的变量“tracy”,然后这个变量使用了值接收者声明的方法,即使用tracy的值作为接收者进行调用,此时方法notify()会接收到tracy的一个副本,这就是值接收者。

2、类型的指针调用使用值接收者声明的方法

1
2
3
// 指向user类型值的指针也可以调用使用值接收者声明的方法
kobe := &user{"Kobe", "kobe.qq.com"}
kobe.notify()

在这里我们声明了一个名为kobe的指针变量,并给user结构初始化。接着,我们用这个指针变量调用了notify方法,为什么指针接收者可以调用使用值接收者声明的方法呢?其实,为了支持这种调用,Golang调整了指针的值,我们可以认为上面指针变量调用notify方法是执行了下面这种操作:

1
(*kobe).notify()

我们发现,这不是指针解引用吗?没错,是的,指针变量被解引用为值时,这不就符合了值接收者的要求了吗。所以,也就是说指针变量调用使用值接收者声明的方法是合法的,并且其实就是使用类型的值来调用使用值接收者声明的方法。当然,这是Golang编译器背后做的事情。

需要注意的是,此时notify()操作的仍然是值的一个副本,只不过这次操作的副本是kobe指针指向的值的一个副本!

3、类型的值调用使用指针接收者声明的方法

1
2
3
// user类型的值可以用来调用使用指针接收者声明的方法
tracy.changeEmail("guoguo.qq.com")
tracy.notify()

既然类型的指针变量可以调用使用值接收者声明的方法,那么类型的值可以调用使用指针接收者声明的方法吗?答案是可以的。Golang编译器同样为我们调整了值,即上面的tracy.notify()实际上是执行了如下操作:

1
(&tracy).notify()

这样就符合了指针接收者的要求了。

4、类型的指针调用使用指针接收者声明的方法

1
2
3
// 指向user类型值的指针也可以调用使用指针接收者声明的方法
kobe.changeEmail("xiaobudian.qq.com")
kobe.notify()

从代码中我们看到,指向user类型的指针调用了使用指针接收者声明的方法,这里操作的是实际值,并不是副本,并且在调用的方法里对值的修改对于调用者而言是可见的。也就是说,kobe.notify()调用完成之后,email字段被修改为xiaobudian.qq.com,此时在main函数中如果打印kobe.email,那么这个结果应该是被修改后的结果。

讲到这里,大家应该懂了值接收者和指针接收者的区别了吧?

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

最重要的一点是值接收者使用值的副本来调用方法,而指针接受者使用实际值来调用方法