要理解使用指針接收者和使用值接收者的根本區(qū)別只需要明確一點就夠了:它們的方法名是不一樣的障癌。
方法名
我們拿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
類型應該都實現(xiàn)了這個接口夕土,對吧?其實不然瘟判,因為Man
的Say()
方法是指針接收者怨绣,所以嚴格來說是指針類型*Man
實現(xiàn)了這個接口,而值類型Man
并沒有拷获。同理篮撑,因為Woman
的Say()
方法是值類型,所以嚴格來說是Woman
實現(xiàn)了這個接口匆瓜,而*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()方法
媽媽再也不用擔心我搞不清楚指針類型和值類型接收者的區(qū)別了茧妒!