溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Go語言之接口

發(fā)布時(shí)間:2020-06-24 22:31:49 來源:網(wǎng)絡(luò) 閱讀:1340 作者:baby神 欄目:編程語言

接口是一種約定,它是一個(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,然后定義地兩種類型catdog實(shí)現(xiàn)了接口animal。在使用的時(shí)候,分別把類型cat的值c、類型dog的值d賦值給接口animal的值a,然后分別執(zhí)行aprintInfo方法,可以看到不同的輸出。


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)指針接收者的接口。


向AI問一下細(xì)節(jié)

免責(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)容。

AI