您好,登錄后才能下訂單哦!
接口是一種約定,它是一個(gè)抽象的類型,和我們見到的具體的類型如int、map、slice等不一樣。具體的類型,我們可以知道它是什么,并且可以知道可以用它做什么;但是接口不一樣,接口是抽象的,它只有一組接口方法,我們并不知道它的內(nèi)部實(shí)現(xiàn),所以我們不知道接口是什么,但是我們知道可以利用它提供的方法做什么。
抽象就是接口的優(yōu)勢(shì),它不用和具體的實(shí)現(xiàn)細(xì)節(jié)綁定在一起,我們只需定義接口,告訴編碼人員它可以做什么,這樣我們可以把具體實(shí)現(xiàn)分開,編碼就會(huì)更加靈活方面,適應(yīng)能力也會(huì)非常強(qiáng)。
func main() { var b bytes.Buffer fmt.Fprint(&b,"Hello World") fmt.Println(b.String()) }
以上就是一個(gè)使用接口的例子,我們先看下fmt.Fprint
函數(shù)的實(shí)現(xiàn)。
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
函數(shù)的第一個(gè)參數(shù)是io.Writer
這個(gè)接口,所以只要實(shí)現(xiàn)了這個(gè)接口的具體類型都可以作為參數(shù)傳遞給fmt.Fprint
函數(shù)。而bytes.Buffer
恰恰實(shí)現(xiàn)了io.Writer
接口,所以可以作為參數(shù)傳遞給fmt.Fprint
函數(shù)。
內(nèi)部實(shí)現(xiàn)
我們前面提過接口是用來定義行為類型的,它是抽象的,這些定義的行為不是由接口直接實(shí)現(xiàn),而是通過方法由用戶定義的類型實(shí)現(xiàn)。如果用戶定義的類型,實(shí)現(xiàn)了接口類型聲明的所有方法,那么這個(gè)用戶定義的類型就實(shí)現(xiàn)了這個(gè)接口,所以這個(gè)用戶定義類型的值就可以賦值給接口類型的值。
func main() { var b bytes.Buffer fmt.Fprint(&b, "Hello World") var w io.Writer w = &b fmt.Println(w) }
這個(gè)例子中,因?yàn)?/span>bytes.Buffer
實(shí)現(xiàn)了接口io.Writer
,所以我們可以通過w = &b
賦值,這個(gè)賦值的操作會(huì)把定義類型的值存入接口類型的值。
賦值操作執(zhí)行后,如果我們對(duì)接口方法執(zhí)行調(diào)用,其實(shí)是調(diào)用存儲(chǔ)的用戶定義類型的對(duì)應(yīng)方法,這里我們可以把用戶定義的類型稱之為實(shí)體類型
。
我們可以定義很多類型,讓它們實(shí)現(xiàn)一個(gè)接口,那么這些類型都可以賦值給這個(gè)接口。這時(shí)候接口方法的調(diào)用,其實(shí)就是對(duì)應(yīng)實(shí)體類型
對(duì)應(yīng)方法的調(diào)用,這就是多態(tài)。
func main() { var a animal var c cat a=c a.printInfo() //使用另外一個(gè)類型賦值 var d dog a=d a.printInfo() } type animal interface { printInfo() } type cat int type dog int func (c cat) printInfo(){ fmt.Println("a cat") } func (d dog) printInfo(){ fmt.Println("a dog") }
以上例子演示了一個(gè)多態(tài)。我們定義了一個(gè)接口animal
,然后定義地兩種類型cat
和dog
實(shí)現(xiàn)了接口animal
。在使用的時(shí)候,分別把類型cat
的值c
、類型dog
的值d
賦值給接口animal
的值a
,然后分別執(zhí)行a
的printInfo
方法,可以看到不同的輸出。
a cat a dog
我們看下接口的值被賦值后,接口值內(nèi)部的布局。接口的值是一個(gè)兩個(gè)字長(zhǎng)度的數(shù)據(jù)結(jié)構(gòu),第一個(gè)字包含一個(gè)指向內(nèi)部表結(jié)構(gòu)的指針,這個(gè)內(nèi)部表里存儲(chǔ)的有實(shí)體類型
的信息以及相關(guān)聯(lián)的方法集;第二個(gè)字包含的是一個(gè)指向存儲(chǔ)的實(shí)體類型
值的指針。所以接口的值結(jié)構(gòu)其實(shí)是兩個(gè)指針,這也可以說明接口其實(shí)是一個(gè)引用類型。
方法集
我們都知道,如果要實(shí)現(xiàn)一個(gè)接口,必須實(shí)現(xiàn)這個(gè)接口提供的所有方法。但是實(shí)現(xiàn)方法的時(shí)候,我們可以使用指針接收者實(shí)現(xiàn),也可以使用值接收者實(shí)現(xiàn),這兩者是有區(qū)別的。下面我們就好好分析下這兩者的區(qū)別。
func main() { var c cat //值作為參數(shù)傳遞 invoke(c) } //需要一個(gè)animal接口作為參數(shù) func invoke(a animal){ a.printInfo() } type animal interface { printInfo() } type cat int //值接收者實(shí)現(xiàn)animal接口 func (c cat) printInfo(){ fmt.Println("a cat") }
還是原來的例子改改,增加一個(gè)invoke
函數(shù),該函數(shù)接收一個(gè)animal
接口類型的參數(shù),例子中傳遞參數(shù)的時(shí)候,也是以類型cat
的值c
傳遞的,運(yùn)行程序可以正常執(zhí)行。現(xiàn)在我們稍微改造一下,使用類型cat
的指針&c
作為參數(shù)傳遞。
func main() { var c cat //指針作為參數(shù)傳遞 invoke(&c) }
只修改這一處,其他保持不變,我們運(yùn)行程序,發(fā)現(xiàn)也可以正常執(zhí)行。通過這個(gè)例子我們可以得出結(jié)論:實(shí)體類型以值接收者實(shí)現(xiàn)接口的時(shí)候,不管是實(shí)體類型的值,還是實(shí)體類型值的指針,都實(shí)現(xiàn)了該接口。
下面我們把接收者改為指針試試。
func main() { var c cat //值作為參數(shù)傳遞 invoke(c) } //需要一個(gè)animal接口作為參數(shù) func invoke(a animal){ a.printInfo() } type animal interface { printInfo() } type cat int //指針接收者實(shí)現(xiàn)animal接口 func (c *cat) printInfo(){ fmt.Println("a cat") }
這個(gè)例子中把實(shí)現(xiàn)接口的接收者改為指針,但是傳遞參數(shù)的時(shí)候,我們還是按值進(jìn)行傳遞,點(diǎn)擊運(yùn)行程序,會(huì)出現(xiàn)以下異常提示:
./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)
提示中已經(jīng)很明顯地告訴我們,說cat
沒有實(shí)現(xiàn)animal
接口,是因?yàn)?/span>printInfo
方法有一個(gè)指針接收者,所以cat
類型的值c
不能作為接口類型animal
傳參使用。下面我們?cè)偕晕⑿薷南?,改為以指針作為參?shù)傳遞。
func main() { var c cat //指針作為參數(shù)傳遞 invoke(&c) }
其他都不變,只是把以前使用值的參數(shù),改為使用指針作為參數(shù),我們?cè)龠\(yùn)行程序,就可以正常運(yùn)行了。由此可見實(shí)體類型以指針接收者實(shí)現(xiàn)接口的時(shí)候,只有指向這個(gè)類型的指針才被認(rèn)為實(shí)現(xiàn)了該接口。
現(xiàn)在我們總結(jié)下這兩種規(guī)則,首先以方法接收者是值還是指針的角度看。
Methods Receivers | Values |
---|---|
(t T) | T and *T |
(t *T) | *T |
上面的表格可以解讀為:如果是值接收者,實(shí)體類型的值和指針都可以實(shí)現(xiàn)對(duì)應(yīng)的接口;如果是指針接收者,那么只有類型的指針能夠?qū)崿F(xiàn)對(duì)應(yīng)的接口。
其次我們以實(shí)體類型是值還是指針的角度看。
Values | Methods Receivers |
---|---|
T | (t T) |
*T | (t T) and (t *T) |
上面的表格可以解讀為:類型的值只能實(shí)現(xiàn)值接收者的接口;指向類型的指針,既可以實(shí)現(xiàn)值接收者的接口,也可以實(shí)現(xiàn)指針接收者的接口。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。