Go方法接收者值接收者與指針接收者詳解
引言
在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方法值接收者指針接收者的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Go語言中interface語法與使用詳解
- go語言實現接口查詢
- 基於go interface{}==nil 的幾種坑及原理分析
- Go 通過結構struct實現接口interface的問題
- Go 語言選擇器實例教程