要理解使用指針接收者和使用值接收者的根本區別只需要明確一點就夠了:它們的方法名是不一樣的。
方法名
我們拿Man
和Woman
兩個簡單的結構體舉例:
type Man struct {
}
type Woman struct {
}
我們分別使用指針接收者和值接收者給它們添加一個Say()
方法:
// Say()方法的全名爲(*Man).Say()
// 即只有指針類型*Man纔有Say()方法
func (*Man) Say() {
}
// Say()方法的全名爲(Woman).Say()
// 只有值類型Woman纔有Say()方法
func (Woman) Say() {
}
這裏雖然它們都是Say()
方法,但實際上方法名是不一樣的,如果你使用指針接收者,方法的全名爲(*Man).Say()
, 如果是值類型,則全名爲Woman.Say()
。嚴格的來說,對於前者,只能使用(*Man)
類型來調用Say()
方法,後者則是隻能使用Woman
類型來調用,因爲值類型Man
並沒有Say()
方法,同理指針類型*Woman
也沒有Say()
方法。
編譯器隱式轉換
那麼問題來了,爲什麼在實際編碼時,使用Man.Say()
也能通過編譯呢?如:
man := Man{} // man是個值類型
man.Say() // ok, 編譯器將man隱式轉換成了&Man
這是因爲go的編譯器爲了我們做了一次隱式轉換,即將man.Say()
轉換成了(&man).Say()
,也就是對man
做了取地址操作。同理,如果實參是值類型而形參(方法接收者)是指針類型:
ptrWoman := &Woman{}
ptrWoman.Say() // ok, 編譯器將ptrWoman隱式轉換成了*Woman
編譯器也會爲了通過編譯而儘量把指針類型ptrWoman
向Woman
類型上"套",這個"套"法就是對ptrWoman
做隱式轉換,轉換成(*ptrWoman).Say()
,這樣就跟方法名匹配上了。
那麼既然編譯器這麼勤勞,爲什麼我們還需要關心這個問題呢?原因是對於接口類型,編譯器"偷懶"了,並不會主動爲我們做類型轉換,比如我們定義一個CanTalk
接口,裏面就有這個Say()
方法:
// 定義一個說話接口
type CanTalk interface {
// 說話方法
Say()
}
這樣一來,Woman
和Man
類型應該都實現了這個接口,對吧?其實不然,因爲Man
的Say()
方法是指針接收者,所以嚴格來說是指針類型*Man
實現了這個接口,而值類型Man
並沒有。同理,因爲Woman
的Say()
方法是值類型,所以嚴格來說是Woman
實現了這個接口,而*Woman
則沒有。所以,如果你把值類型Man
的變量賦值給接口CanTalk
是會報錯的:
var canTalk CanTalk
canTalk = man // error, Man類型沒有Say()方法
// cannot use man (type Man) as type CanTalk in assignment:
// Man does not implement CanTalk (Say method has pointer receiver)
而反過來,如果將指針類型*Man
的變量賦值給CanTalk
則沒有問題:
canTalk = ptrMan // ok, *Man類型有Say()方法
媽媽再也不用擔心我搞不清楚指針類型和值類型接收者的區別了!