Go错误集锦 | 方法接收者的值类型和指针类型

2022年2月13日 273点热度 0人点赞 0条评论


我们知道,在Go中定义了结构体后,可以给该结构体定义方法。如下:

type customer struct {    balance string}
func (c customer) SetBalance(v float64) { c.balance = v}
func (c *customer) UpdateBalance(v float64) { c.balance = v}

那么接收者的值类型和指针类型他们之间有什么区别?我们在定义方法时,接收者是该选择使用值类型还是选择使用指针类型呢?


01 方法接收者是值类型 

在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。如图所示:

图片

02 方法接收者是指针类型 

如果接收者的类型是指针,那么,我们传递给方法的是原对象的地址,依然是值拷贝,这里的值是地址值,而非是原对象的拷贝。这时,在方法中对接收者的任何改变,都会作用到原对象上。

依然是上面的示例,我们将接收者类型更改成指针。

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。如图所示: 图片

03 接收者的类型该如何选择

在定义结构体方法时,接收者类型是使用值类型还是指针类型呢?下面我们列出一些常见的选择依据来帮助我们选择使用哪种类型。

接收者必须是指针类型的场景:

  • 如果方法需要对接收者进行改变时,则必须是指针类型。这条规则同样适用于切片类型。如果接收者类型是一个切片,同时在方法中我们想在切片中增加元素时,如下:

type slice []intfunc (s *slice) add(element int) {    *s = append(*s, element)}

  • 如果接收者包含有不能拷贝的字段时,则必须是指针类型。例如sync包中的类型字段是不能被拷贝的。

接收者建议使用指针类型的场景:

  • 如果接收者是一个很大的对象时,建议优先使用指针类型。使用指针类型能够进行快速拷贝,可以提高调用方法的效率。那么,多大的才算是大对象呢,这没有标准,一般建议是在实际项目中通过基准测试来决定。

接收者必须是值类型的场景:

  • 当必须保持接收者的不变性时,即在函数中不能改变原有对象时。

  • 当接收者是map、function或channel类型时。否则,会导致编译错误。

接收者建议使用值类型的场景:

  • 当接收者是一个不被改变的切片类型时。

  • 当接收者的类型是一个基础的类型时。Go的基础类型包括Numbers、strings、boolean。

  • 当接收者是一个小对象同时不符合使用指针的条件时。

04 一个示例 

下面我们看一个稍微复杂点示例。在该示例中,customer结构体中包含了一个指针类型的字段。示例如下:

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的值。

28110Go错误集锦 | 方法接收者的值类型和指针类型

这个人很懒,什么都没留下

文章评论