首頁 > 軟體

Go方法接收者值接收者與指標接收者詳解

2022-11-03 14:01:45

引言

在review 一些程式碼中,發現經常某個型別定義的方法,其接收者既有值型別,又有指標型別,然後 Goland 就有提示: Struct Person has methods on both value and pointer receivers. Such usage is not recommended by the Go Documentation.

一般來講,這個提示對程式碼的執行並不會產生什麼問題。只不過對於有輕微 “程式碼潔癖” 的人來講,體感不好,就一定想要改統一。

當然,我並不是想講要統一的問題,前面說這麼多廢話,只是為了鋪墊一下引出本文的內容:Go中的值接收者與指標接收者有什麼關係與區別,該怎麼選?

聯絡與區別

在繼續講下去之前,我們得先明確,Go 裡邊能夠定義方法的必須是自定義型別,而不能是系統內建型別,比如 int、string 這種是不可以為其新增方法的。

那麼當我們定義了一個自定義型別,可以為其新增方法,先上程式碼:

package main
import "fmt"
type Person struct {
   name string
   age  int
}
// 值針接收者
func (p Person) GetName() string {
   return p.name
}
// 指標接收者
func (p *Person) GetAge() int {
   return p.age
}
func main() {
   //  定義了一個【值型別】
   t := Person{
      name: "DaYu",
      age:  int(28),
   }
   // 呼叫值方法
   fmt.Println(t.GetName())
   // 呼叫指標方法
   fmt.Println(t.GetAge())
}
-----執行結果-------
study/demo01/client go run *
DaYu
28

指標型別呼叫結果

從使用過程看,值型別的變數,可以呼叫該型別的值接收者方法,也可以呼叫指標接收者方法。

反之,我們可以定義一個指標型別,然後看看呼叫結果:

package main
import "fmt"
type Person struct {
   name string
   age  int
}
func (p *Person) GetName() string {
   return p.name
}
func (p Person) GetAge() int {
   return p.age
}
func main() {
   // 注意,其它地方都沒有改,只是這裡改變了型別
   t := &Person{
      name: "DaYu",
      age:  int(28),
   }
   fmt.Println(t.GetName())
   fmt.Println(t.GetAge())
}
-----執行結果-------
study/demo01/client go run *
DaYu
28

這段程式碼告訴我們,指標型別的變數,可以呼叫該型別的值接收者方法,也可以呼叫指標接收者方法。

是不是特別有意思?

  • 值型別變數,可以呼叫值接收的方法,也可以呼叫指標接收者的方法;
  • 指標型別變數,可以呼叫值接收的方法,也可以呼叫指標接收者的方法。

看起來好像兩者對等的,並沒有差別。那麼二者真的沒有差別嗎?只是一種表達形式上的差異?其實不然,如果引入介面型別後,我們再來看看。

package main
// 新增的介面
type Animal interface {
   GetName() string
   GetAge() int
}
type Person struct {
   name string
   age  int
}
func (p *Person) GetName() string {
   return p.name
}
func (p Person) GetAge() int {
   return p.age
}
func main() {
   // 定義的介面變數
   var ani Animal
   // person 實現了 Animal 介面,賦值給了 ani 變數
   // 但是,這裡編譯會通不過,錯誤如下:
   // Cannot use 'Person{ name: "DaYu", age: int(28), }' (type Person) as the type Animal Type does not implement 'Animal' as the 'GetName' method has a pointer receiver
   ani = Person{
      name: "DaYu",
      age:  int(28),
   }
   ani.GetName()
   ani.GetAge()
}

為什麼會報錯呢? 錯誤提醒很明顯了:Person 沒有實現 Animal 的 GetName 方法。因為在上面的程式碼中,我們實現 GetName 方法的是 (*Person) 型別。

但是為什麼 GetAge 方法不報錯呢? 那是因為 Go 裡邊對於 (Type)Method 的方法,會自動讓他擁有 (*Type)Method 方法的能力。

實現介面時約束

  • 如果定義的是 (Type)Method,則該型別會隱式的宣告一個 (*Type)Method;
  • 如果定義的是 (*Type)Method ,則不會隱式什麼一個 (Type)Method。

至於為什麼不也隱式申明一個 (Type)Method ,我覺得有一個原因是,我們一般採用指標接收者時,方法內部改變的值,接收者本身也會改變,那麼此時如果隱式有這樣一個申明,外部使用值型別時,這個改變就不會生效,語意上就會非常奇怪。

該怎麼用

從使用表現上看,指標接收者在方法內部的改變,會體現到其本身。但這並不是決定我們要不要用指標接收者的唯一理由! 最重要的還是看接收者要不要全域性共用一個實體,其次某些場景下,如果接收者本身太大,拷貝成本很高,也應該使用指標接收者。

回到檔案開篇的問題,為什麼不建議值接收者、指標接收者混用,主要還是在於語意不夠清晰,存在潛在理解成本的問題。

以上就是Go方法接收者值接收者與指標接收者詳解的詳細內容,更多關於Go方法值接收者指標接收者的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com