0x00 簡單method的規則
首先,我們宣告示例的基本操作結構體。
1 2 3 4 |
type Cat struct{ Name string Color string } |
我們宣告瞭一個 Cat 的 type,在它的基礎上我們宣告一個Meow的method:
1 2 3 |
func (c Cat) Meow() { fmt.Println("Name:", c.Name, "Color:", c.Color) } |
在上面的程式碼上, 我們宣告瞭一個method, 它的receiver是Cat(區別於Cat的指標),在這時,無論是我們通過 Cat型別的變數來呼叫method還是用 *Cat的型別來呼叫,Go都會正常的編譯並正常輸出,如下圖:
如圖,main函式中的 a 和 b 變數,一個是 Cat 型別,一個是Cat的指標型別,他們都有Meow方法。
那麼如果我們宣告的函式receiver是 *Cat呢?
1 2 3 |
func (c *Cat) Meow() { fmt.Println("Name:", c.Name, "Color:", c.Color) } |
在這種情況下,方法也是繼承的:
上面是簡單的 Cat 和 *Cat 上的方法集的繼承規則:
普通情況下,型別 T 和 *T 上的方法集是互相繼承的。
0x01 介面中methods set的規則
接下來我們宣告一個介面來做對比:
1 2 3 4 5 6 7 8 |
type Meower interface{ Meow() } func Greet(meower Meower){ meower.Meow() } |
首先我們在Cat型別上實現Meower介面:
1 2 3 |
func (c Cat) Meow(){ fmt.Println("Name:", c.Name, " Color:", c.Color) } |
此時編譯是沒問題的
也就是說,如果我們給Cat型別實現Meower的介面上Meow的方法,無論是Cat還是*Cat都是可以成為介面呼叫的。
那如果我們實現介面方式時候,選擇的receiver是*Cat呢?
1 2 3 |
func (c *Cat) Meow(){ fmt.Println("Name:", c.Name, " Color:", c.Color) } |
編譯不通過,如下圖:
如果給指標實現Meow方法,在第26行出現了型別錯誤,提示我們Meow方法的receiver是一個指標型別,說明此時方法不能繼承。
於是這裡的規則是:
在介面中的method,對於普通型別T:
T的methods set裡不會繼承包含*T實現的method,除非T自己實現相對應的method。
但是,*T會繼承T的method set。
0x02 嵌入型別中methods set的規則
我們討論了上面兩種情況,那考慮如果Cat作為嵌入型別(Embedded Types)時會發生什麼呢?
於是在上面的基礎上,我們來宣告這樣一個型別來做實驗:
1 2 3 4 |
type BlackCat struct{ Cat Age int } |
此時我們是直接可以通過BlackCat 來呼叫Cat實現的介面method的, 如圖:
上圖中,我們並沒有為BlackCat實現介面,僅僅為Cat實現了介面,不過BlackCat裡面嵌入了Cat的一個內部型別,也是可以通過介面呼叫函式Greet來呼叫到Meow method。
同時我們修改main函式,發現BlackCat的methods set 裡面包含了Meow(), 如下圖:
此時:
嵌入型別的型別中,外部型別自己未曾實現的methods被攜帶的內部函式實現時,外部型別也會將這些methods加入到自己的methods set裡。
那麼,如果外部型別BlackCat自己實現了Meow()函式(同時也實現了介面),內部和外部都實現的情況下會怎樣呢?
下面我們給BlackCat實現Meow()方法試一下:
1 2 3 |
func (c BlackCat) Meow(){ fmt.Println("BlackName:", c.Cat.Name, " Age:", c.Age) } |
首先,編譯是沒問題的:
我們看一下a.Cat.Meow() 和 a.Meow()的執行結果:
發現BlackCat的使用了自己實現的方法,或者說它把內部Cat的Meow()方法覆蓋了。
將直接呼叫改成使用介面時也是如此:
兩次結果是一樣的。
0x03 結論
搞清Golang中的方法集繼承對我們寫出不囉嗦的程式碼很有用處,而在Golang中,對於指標的處理也比C/C++中靈活了一些。
0x04 參考資料
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式