Go中方法接收者是指針類型和值類型的根本區別, 看這一篇就足夠了

要理解使用指針接收者和使用值接收者的根本區別只需要明確一點就夠了:它們的方法名是不一樣的。

方法名

我們拿ManWoman兩個簡單的結構體舉例:

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

編譯器也會爲了通過編譯而儘量把指針類型ptrWomanWoman類型上"套",這個"套"法就是對ptrWoman做隱式轉換,轉換成(*ptrWoman).Say(),這樣就跟方法名匹配上了。

那麼既然編譯器這麼勤勞,爲什麼我們還需要關心這個問題呢?原因是對於接口類型,編譯器"偷懶"了,並不會主動爲我們做類型轉換,比如我們定義一個CanTalk接口,裏面就有這個Say()方法:

// 定義一個說話接口
type CanTalk interface {
	// 說話方法
	Say()
}

這樣一來,WomanMan類型應該都實現了這個接口,對吧?其實不然,因爲ManSay()方法是指針接收者,所以嚴格來說是指針類型*Man實現了這個接口,而值類型Man並沒有。同理,因爲WomanSay()方法是值類型,所以嚴格來說是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()方法

媽媽再也不用擔心我搞不清楚指針類型和值類型接收者的區別了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章