Go错误集锦 | 聊聊方法接收者的值类型和指针类型之间的区别

yudotyang · 2022-02-14 21:43:52 · 745 次点击 · 预计阅读时间 3 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2022-02-14 21:43:52 的文章,其中的信息可能已经有所发展或是发生改变。

大家好,我是『Go学堂』的渔夫子。今天跟大家聊聊方法接收者类型的话题。

原文链接:https://mp.weixin.qq.com/s/Av3DrzDXa2cjjBbtkj6wuw

我们知道,在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中,大家都听过的一切都是拷贝。所以,当方法的接收者是一个值类型时,实际上是对原来对象的一个拷贝,然后让该对象的拷贝再来调用对应的方法。在方法中对接收者的任何改变,都不会影响原对象。 ​

下面的通过一段具体的示例来说明。

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

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

02-函数接收者-指针类型.png

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

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

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

  • 如果方法需要对接收者进行改变时,则必须是指针类型。这条规则同样适用于切片类型。如果接收者类型是一个切片,同时在方法中我们想在切片中增加元素时,如下:
type slice []int
func (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的值。 ​


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

745 次点击  ∙  1 赞  
加入收藏 微博
1 回复  |  直到 2022-02-15 09:34:39
Mericusta
Mericusta · #1 · 3年之前

所以GO什么时候加上const修饰符让我既可以不用拷贝又可以定义防止self指针被修改:)

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传