這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org
,第一時間看後續筆記。
介面是一種約定,它是一個抽象的類型,和我們見到的具體的類型如int、map、slice等不一樣。具體的類型,我們可以知道它是什麼,並且可以知道可以用它做什麼;但是介面不一樣,介面是抽象的,它只有一組介面方法,我們並不知道它的內部實現,所以我們不知道介面是什麼,但是我們知道可以利用它提供的方法做什麼。
抽象就是介面的優勢,它不用和具體的實現細節綁定在一起,我們只需定義介面,告訴編碼人員它可以做什麼,這樣我們可以把具體實現分開,這樣編碼就會更加靈活方面,適應能力也會非常強。
12345 |
func main() {var b bytes.Bufferfmt.Fprint(&b,"Hello World")fmt.Println(b.String())} |
以上就是一個使用介面的例子,我們先看下fmt.Fprint
函數的實現。
1234567 |
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {p := newPrinter()p.doPrint(a)n, err = w.Write(p.buf)p.free()return} |
從上面的原始碼中,我們可以看到,fmt.Fprint
函數的第一個參數是io.Writer
這個介面,所以只要實現了這個介面的具體類型都可以作為參數傳遞給fmt.Fprint
函數,而bytes.Buffer
恰恰實現了io.Writer
介面,所以可以作為參數傳遞給fmt.Fprint
函數。
內部實現
我們前面提過介面是用來定義行為的類型,它是抽象的,這些定義的行為不是由介面直接實現,而是通過方法由使用者定義的類型實現。如果使用者定義的類型,實現了介面型別宣告的所有方法,那麼這個使用者定義的類型就實現了這個介面,所以這個使用者定義型別的值就可以賦值給介面類型的值。
1234567 |
func main() {var b bytes.Bufferfmt.Fprint(&b, "Hello World")var w io.Writerw = &bfmt.Println(w)} |
這裡例子中,因為bytes.Buffer
實現了介面io.Writer
,所以我們可以通過w = &b
賦值,這個賦值的操作會把定義類型的值存入介面類型的值。
賦值操作執行後,如果我們對介面方法執行調用,其實是調用儲存的使用者定義型別的對應方法,這裡我們可以把使用者定義的類型稱之為實體類型
。
我們可以定義很多類型,讓它們實現一個介面,那麼這些類型都可以賦值給這個介面,這時候介面方法的調用,其實就是對應實體類型
對應方法的調用,這就是多態。
1234567891011121314151617181920212223242526 |
func main() {var a animalvar c cata=ca.printInfo()//使用另外一個類型賦值var d doga=da.printInfo()}type animal interface {printInfo()}type cat inttype dog intfunc (c cat) printInfo(){fmt.Println("a cat")}func (d dog) printInfo(){fmt.Println("a dog")} |
以上例子示範了一個多態。我們定義了一個介面animal
,然後定義了兩種類型cat
和dog
實現了介面animal
。在使用的時候,分別把類型cat
的值c
、類型dog
的值d
賦值給介面animal
的值a
,然後分別執行a
的printInfo
方法,可以看到不同的輸出。
我們看下介面的值被賦值後,介面值內部的布局。介面的值是一個兩個字長度的資料結構,第一個字包含一個指向內部表結構的指標,這個內部表裡儲存的有實體類型
的資訊以及相關聯的方法集;第二個字包含的是一個指向儲存的實體類型
值的指標。所以介面的值結構其實是兩個指標,這也可以說明介面其實一個參考型別。
方法集
我們都知道,如果要實現一個介面,必須實現這個介面提供的所有方法,但是實現方法的時候,我們可以使用指標接收者實現,也可以使用值接收者實現,這兩者是有區別的,下面我們就好好分析下這兩者的區別。
1234567891011121314151617181920 |
func main() {var c cat//值作為參數傳遞invoke(c)}//需要一個animal介面作為參數func invoke(a animal){a.printInfo()}type animal interface {printInfo()}type cat int//值接收者實現animal介面func (c cat) printInfo(){fmt.Println("a cat")} |
還是原來的例子改改,增加一個invoke
函數,該函數接收一個animal
介面類型的參數,例子中傳遞參數的時候,也是以類型cat
的值c
傳遞的,運行程式可以正常執行。現在我們稍微改造一下,使用類型cat
的指標&c
作為參數傳遞。
12345 |
func main() {var c cat//指標作為參數傳遞invoke(&c)} |
只修改這一處,其他保持不變,我們運行程式,發現也可以正常執行。通過這個例子我們可以得出結論:實體類型以值接收者實現介面的時候,不管是實體類型的值,還是實體類型值的指標,都實現了該介面。
下面我們把接收者改為指標試試。
1234567891011121314151617181920 |
func main() {var c cat//值作為參數傳遞invoke(c)}//需要一個animal介面作為參數func invoke(a animal){a.printInfo()}type animal interface {printInfo()}type cat int//指標接收者實現animal介面func (c *cat) printInfo(){fmt.Println("a cat")} |
這個例子中把實現介面的接收者改為指標,但是傳遞參數的時候,我們還是按值進行傳遞,點擊運行程式,會出現以下異常提示:
12 |
./main.go:10: cannot use c (type cat) as type animal in argument to invoke:cat does not implement animal (printInfo method has pointer receiver) |
提示中已經很明顯的告訴我們,說cat
沒有實現animal
介面,因為printInfo
方法有一個指標接收者,所以cat
類型的值c
不能作為介面類型animal
傳參使用。下面我們再稍微修改下,改為以指標作為參數傳遞。
12345 |
func main() {var c cat//指標作為參數傳遞invoke(&c)} |
其他都不變,只是把以前使用值的參數,改為使用指標作為參數,我們再運行程式,就可以正常運行了。由此可見實體類型以指標接收者實現介面的時候,只有指向這個類型的指標才被認為實現了該介面
現在我們總結下這兩種規則,首先以方法接收者是值還是指標的角度看。
Methods Receivers |
Values |
(t T) |
T and *T |
(t *T) |
*T |
上面的表格可以解讀為:如果是值接收者,實體類型的值和指標都可以實現對應的介面;如果是指標接收者,那麼只有類型的指標能夠實現對應的介面。
其次我們我們以實體類型是值還是指標的角度看。
Values |
Methods Receivers |
T |
(t T) |
*T |
(t T) and (t *T) |
上面的表格可以解讀為:類型的值只能實現值接收者的介面;指向類型的指標,既可以實現值接收者的介面,也可以實現指標接收者的介面。
標準庫
《Go語言實戰》讀書筆記,未完待續,歡迎掃碼關注公眾號flysnow_org
,第一時間看後續筆記。