大家好,我是『Go学堂』的渔夫子。今天跟大家聊聊方法接收者类型的话题。
原文链接:https://mp.weixin.qq.com/s/Av3DrzDXa2cjjBbtkj6wuw
我们知道,在Go中定义了结构体后,可以给该结构体定义方法。如下:
```go
type customer struct {
balance string
}
func (c customer) SetBalance(v float64) {
s.balance = v
}
func (c *customer) UpdateBalance(v float64) {
c.balance = v
}
```
这里的c就是方法的接收者。该接收者的类型可以是一个对象的值,也可以是一个对象的指针。
那么接收者的值类型和指针类型他们之间有什么区别?我们在定义方法时,接收者是该选择使用值类型还是选择使用指针类型呢?
**01 方法接收者是值类型**
在Go中,大家都听过的一切都是拷贝。所以,当方法的接收者是一个值类型时,实际上是对原来对象的一个拷贝,然后让该对象的拷贝再来调用对应的方法。在方法中对接收者的任何改变,都不会影响原对象。
下面的通过一段具体的示例来说明。
```go
type customer struct {
balance float64
}
func (c customer) add(v float64) {
c.balance += v
}
func main() {
c := customer{balance: 100.}
c.add(50.)
fmt.Printf("balance: %.2f\n", c.balance)
}
```
因为在add方法中,接收者是值类型,在执行c.add(50.)函数时,实际上是对c进行了拷贝,然后改变了新拷贝的对象的balance。所以,最终c.balance的结果没有任何改变,依然是100。如图所示:
![01-函数接收者-值类型.png](https://cdn.gocn.vip/forum-user-images/20220214/4912c94f04ec45cbaed225269b896af8.jpg)
**02 方法接收者是指针类型**
如果接收者的类型是指针,那么,我们传递给方法的是原对象的地址,依然是值拷贝,这里的值是地址值,而非是原对象的拷贝。这时,在方法中对接收者的任何改变,都会作用到原对象上。
依然是上面的示例,我们将接收者类型更改成指针。
```go
type customer struct {
balance float64
}
func (c *customer) add(v float64) {
c.balance += v
}
func main() {
c := customer{balance: 100.}
c.add(50.)
fmt.Printf("balance: %.2f\n", c.balance)
}
```
因为接收者是指针类型,所以,对balance的更改实际上是对原对象的更改,最终结果会输出150。如图所示:
![02-函数接收者-指针类型.png](https://cdn.gocn.vip/forum-user-images/20220214/56905b82d5cb49bbb747369a70c158a2.jpg)
**03 接收者的类型该如何选择**
在定义结构体方法时,接收者类型是使用值类型还是指针类型呢?下面我们列出一些常见的选择依据来帮助我们选择使用哪种类型。
接收者必须是指针类型的场景:
- 如果方法需要对接收者进行改变时,则必须是指针类型。这条规则同样适用于切片类型。如果接收者类型是一个切片,同时在方法中我们想在切片中增加元素时,如下:
```go
type slice []int
func (s *slice) add(element int) {
*s = append(*s, element)
}
```
- 如果接收者包含有不能拷贝的字段时,则必须是指针类型。例如sync包中的类型字段是不能被拷贝的。
接收者建议使用指针类型的场景:
- 如果接收者是一个很大的对象时,建议优先使用指针类型。使用指针类型能够进行快速拷贝,可以提高调用方法的效率。那么,多大的才算是大对象呢,这没有标准,一般建议是在实际项目中通过基准测试来决定。
接收者必须是值类型的场景:
- 当必须保持接收者的不变性时,即在函数中不能改变原有对象时。
- 当接收者是map、function或channel类型时。否则,会导致编译错误。
接收者建议使用值类型的场景:
- 当接收者是一个不被改变的切片类型时。
- 当接收者的类型是一个基础的类型时。Go的基础类型包括Numbers、strings、boolean。
- 当接收者是一个小对象同时不符合使用指针的条件时。
**04 一个示例**
下面我们看一个稍微复杂点示例。在该示例中,customer结构体中包含了一个指针类型的字段。示例如下:
```go
type customer struct {
card *card
}
type card struct {
balance float64
}
func (c customer) add(operation float64) {
c.card.balance += operation
}
func main() {
c := customer{card: &card{
balance: 100,
}}
c.add(50.)
fmt.Printf("balance: %.2f\n", c.card.balance)
}
```
在该示例中,balance是card结构体中的字段,而customer中通过指针引入了card。同时,方法的接收者类型我们依然使用的是值类型,但最终结果依然会改变原对象中balance的值。
有疑问加站长微信联系(非本文作者))