初學go時很多同學會把
值接收者和指針接收者的方法相互調用搞混淆,好多同學都只記得指針類型可以調用值接收者方法和指針接收者方法,而值類型只能調用值接收者方法,其實不然,在某些情況下,值類型也是可以調用指針接收者方法的。最近又看到有同學發出了這樣的疑問,所以打算記錄一下,用以備忘、分享。
類型不同可以調用
package main import ( "fmt" ) type field struct { name string } func (p *field) pointerMethod() { fmt.Println(p.name) } func (p field) valueMethod() { fmt.Println(p.name) } func main() { fp := &field{name: "pointer"} fv := field{name: "value"} fp.pointerMethod() fp.valueMethod() fv.pointerMethod() fv.valueMethod() } //output: pointer pointer value value
首先我們給field定義了兩個方法,一個是指針接收者的pointerMethod,一個是值接收者valueMethod。
然后我們創建了變量,fp是指針類型,fv是值類型。
fp、fv分別調用pointerMethod、valueMethod,可以看到他們都可以通過編譯正常輸出。
當類型和方法的接收者類型不同時,編譯器會做一些操作:
在值類型調用指針接收者方法時,實際為(&fv).pointerMethod()
在指針類型調用值接收者方法時,實際為(*fp).valueMethod()
這里是類型不同可以相互調用的情況,再說下不能調用的情況。
類型不同不可以調用
不能調用的情況有兩種:
值類型不能被尋址- 用
指針接收者實現接口
兩種情況都是值類型不能調用指針接收者方法
值類型不能被尋址
如果值類型實體不能被尋址,那么它就不能調用指針接收者方法
package main import ( "fmt" ) type field struct { name string } func (p *field) pointerMethod() { fmt.Println(p.name) } func (p field) valueMethod() { fmt.Println(p.name) } func NewFiled() field { return field{name: "right value struct"} } func main() { NewFiled().valueMethod() NewFiled().pointerMethod() }
運行代碼報錯:
./x.go:37:12: cannot call pointer method on NewFiled() ./x.go:37:12: cannot take the address of NewFiled()
- 看下青藤木鳥的解釋:
看來編譯器首先試着給
NewFoo()返回的右值調用 pointer method,出錯;然后試圖給其插入取地址符,未果,就只能報錯了。至於左值和右值的區別,大家感興趣可以自行搜索一下。大致來說,最重要區別就是是否可以被尋址,可以被尋址的是左值,既可以出現在賦值號左邊也可以出現在右邊;不可以被尋址的即為右值,比如函數返回值、字面值、常量值等等,只能出現在賦值號右邊。
用指針接收者實現接口
使用指針接收者實現接口方法,那么只有指針類型的實體實現了接口
package main import "fmt" type human interface { speak() sing() } type man struct { } func (m man) speak() { fmt.Println("speaking") } func (m *man) sing() { fmt.Println("singing") } func main() { var h human = &man{} h.speak() h.sing() }
上面代碼可以正常編譯輸出,但是我們把&man{}修改為man{}就編譯不過了
func main() { var h human = man{} h.speak() h.sing() }
報錯如下:
./x.go:22:6: cannot use man literal (type man) as type human in assignment: man does not implement human (sing method has pointer receiver)
說man沒有實現human,因為sing是個指針接收者方法。
這里看下飛雪無情的總結:
實體類型以指針接收者實現接口的時候,只有指向這個類型的指針才被認為實現了該接口
如果是值接收者,實體類型的值和指針都可以實現對應的接口;如果是指針接收者,那么只有類型的指針能夠實現對應的接口
再看下饒大的解釋:
接收者是指針類型的方法,很可能在方法中會對接收者的屬性進行更改操作,從而影響接收者;而對於接收者是值類型的方法,在方法中不會對接收者本身產生影響。
所以,當實現了一個接收者是值類型的方法,就可以自動生成一個接收者是對應指針類型的方法,因為兩者都不會影響接收者。但是,當實現了一個接收者是指針類型的方法,如果此時自動生成一個接收者是值類型的方法,原本期望對接收者的改變(通過指針實現),現在無法實現,因為值類型會產生一個拷貝,不會真正影響調用者。
平時我們寫代碼的時候也不用可以記這個,不僅編譯器會報錯,goland也一樣會提示。
參考
https://www.qtmuniao.com/2020/01/06/go-value-pointer-method/
https://qcrao91.gitbook.io/go/interface/zhi-jie-shou-zhe-he-zhi-zhen-jie-shou-zhe-de-qu-bie
https://www.flysnow.org/2017/04/03/go-in-action-go-interface.html
