Go語言實戰筆記(九)| Go 介面

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

《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,然後定義了兩種類型catdog實現了介面animal。在使用的時候,分別把類型cat的值c、類型dog的值d賦值給介面animal的值a,然後分別執行aprintInfo方法,可以看到不同的輸出。

12
a cata dog

我們看下介面的值被賦值後,介面值內部的布局。介面的值是一個兩個字長度的資料結構,第一個字包含一個指向內部表結構的指標,這個內部表裡儲存的有實體類型的資訊以及相關聯的方法集;第二個字包含的是一個指向儲存的實體類型值的指標。所以介面的值結構其實是兩個指標,這也可以說明介面其實一個參考型別。

方法集

我們都知道,如果要實現一個介面,必須實現這個介面提供的所有方法,但是實現方法的時候,我們可以使用指標接收者實現,也可以使用值接收者實現,這兩者是有區別的,下面我們就好好分析下這兩者的區別。

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,第一時間看後續筆記。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.