Go基礎(chǔ)教程系列之Go接口使用詳解_第1頁
Go基礎(chǔ)教程系列之Go接口使用詳解_第2頁
Go基礎(chǔ)教程系列之Go接口使用詳解_第3頁
Go基礎(chǔ)教程系列之Go接口使用詳解_第4頁
Go基礎(chǔ)教程系列之Go接口使用詳解_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第Go基礎(chǔ)教程系列之Go接口使用詳解但這些行為不會(huì)在接口上直接實(shí)現(xiàn),而是需要用戶自定義的方法來實(shí)現(xiàn)。所以,在上面的Namer接口類型中的方法my_methodN都是沒有實(shí)際方法體的,僅僅只是在接口Namer中存放這些方法的簽名(簽名=函數(shù)名+參數(shù)(類型)+返回值(類型))。

當(dāng)用戶自定義的類型實(shí)現(xiàn)了接口上定義的這些方法,那么自定義類型的值(也就是實(shí)例)可以賦值給接口類型的值(也就是接口實(shí)例)。這個(gè)賦值過程使得接口實(shí)例中保存了用戶自定義類型實(shí)例。

例如:

packagemain

import(

"fmt"

//Shaper接口類型

typeShaperinterface{

Area()float64

//Circlestruct類型

typeCirclestruct{

radiusfloat64

//Circle類型實(shí)現(xiàn)Shaper中的方法Area()

func(c*Circle)Area()float64{

return3.14*c.radius*c.radius

//Squarestruct類型

typeSquarestruct{

lengthfloat64

//Square類型實(shí)現(xiàn)Shaper中的方法Area()

func(s*Square)Area()float64{

returns.length*s.length

funcmain(){

//Circle類型的指針類型實(shí)例

c:=new(Circle)

c.radius=2.5

//Square類型的值類型實(shí)例

s:=Square{3.2}

//Sharpe接口實(shí)例ins1,它自身是指針類型的

varins1Shaper

//將Circle實(shí)例c賦值給接口實(shí)例ins1

//那么ins1中就保存了實(shí)例c

ins1=c

fmt.Println(ins1)

//使用類型推斷將Square實(shí)例s賦值給接口實(shí)例

ins2:=s

fmt.Println(ins2)

上面將輸出:

{2.5}

{3.2}

從上面輸出結(jié)果中可以看出,兩個(gè)接口實(shí)例ins1和ins2被分別賦值后,分別保存了指針類型的Circle實(shí)例c和值類型的Square實(shí)例s。

另外,從上面賦值ins1和ins2的賦值語句上看:

ins1=c

ins2:=s

是否說明接口實(shí)例ins就是自定義類型的實(shí)例?實(shí)際上接口是指針類型(指向什么見下文)。這個(gè)時(shí)候,自定義類型的實(shí)例c、s稱為具體實(shí)例,ins實(shí)例是抽象實(shí)例,因?yàn)閕ns接口中定義的行為(方法)并沒有具體的行為模式,而c、s中的行為是具體的。

因?yàn)榻涌趯?shí)例ins也是自定義類型的實(shí)例,所以當(dāng)接口實(shí)例中保存了自定義類型的實(shí)例后,就可以直接從接口上調(diào)用它所保存的實(shí)例的方法。例如:

fmt.Println(ins1.Area())//輸出19.625

fmt.Println(ins2.Area())//輸出10.24

這里ins1.Area()調(diào)用的是Circle類型上的方法Area(),ins2.Area()調(diào)用的則是Square類型上的方法Area()。這說明Go的接口可以實(shí)現(xiàn)面向?qū)ο笾械亩鄳B(tài):可以按需調(diào)用名稱相同、功能不同的方法。

接口實(shí)例中存的是什么

前面說了,接口類型是指針類型,但是它到底存放了什么東西?

接口類型的數(shù)據(jù)結(jié)構(gòu)是2個(gè)指針,占用2個(gè)機(jī)器字長。

當(dāng)將類型實(shí)例c賦值給接口實(shí)例ins1后,用println()函數(shù)輸出ins1和c,比較它們的地址:

println(ins1)

println(c)

輸出結(jié)果:

(0x4ceb00,0xc042068058)

0xc042068058

從結(jié)果中可以看出,接口實(shí)例中包含了兩個(gè)地址,其中第二個(gè)地址和類型實(shí)例c的地址是完全相同的。而第二個(gè)地址c是Circle的指針類型實(shí)例,所以ins中的第二個(gè)值也是指針。

ins中的第一個(gè)是指針是什么?它所指向的是一個(gè)內(nèi)部表結(jié)構(gòu)iTable,這個(gè)Table中包含兩部分:第一部分是實(shí)例c的類型信息,也就是*Circle,第二部分是這個(gè)類型(Circle)的方法集,也就是Circle類型的所有方法(此示例中Circle只定義了一個(gè)方法Area())。

所以,如圖所示:

注意,上圖中的實(shí)例c是指針,是指針類型的Circle實(shí)例。

對于值類型的Square實(shí)例s,ins2保存的內(nèi)容則如下圖:

實(shí)際上接口實(shí)例中保存的內(nèi)容,在反射(reflect)中體現(xiàn)的淋漓盡致,reflect所有的一切都離不開接口實(shí)例保存的內(nèi)容。

方法集(MethodSet)規(guī)則

官方手冊對MethodSet的解釋:/ref/spec#Method_sets

實(shí)例的methodset決定了它所實(shí)現(xiàn)的接口,以及通過receiver可以調(diào)用的方法。

方法集是類型的方法集合,對于非接口類型,每個(gè)類型都分兩個(gè)MethodSet:值類型實(shí)例是一個(gè)MethodSet,指針類型的實(shí)例是另一個(gè)MethodSet。兩個(gè)MethodSet由不同receiver類型的方法組成:

實(shí)例的類型receiver

--------------------------------------

值類型:T(TType)

指針類型:*T(TType)或(T*Type)

也就是說:

值類型的實(shí)例的MethodSet只由值類型的receiver(TType)組成指針類型的實(shí)例的MethodSet由值類型和指針類型的receiver共同組成,即(TType)和(T*Type)

這是什么意思呢?從receiver的角度去考慮:

receiver實(shí)例的類型

---------------------------

(TType)T或*T

(T*Type)*T

上面的意思是:

receiver是指針類型的方法只可能存在于指針類型的實(shí)例方法集中receiver是值類型的方法既存在于值類型的實(shí)例方法集中,也存在于指針類型的方法集中

從實(shí)現(xiàn)接口方法的角度上看:

如果某類型實(shí)現(xiàn)接口的方法的receiver是(T*Type)類型的,那么只有指針類型的實(shí)例*T才算是實(shí)現(xiàn)了這個(gè)接口,因?yàn)檫@個(gè)方法不在值類型的實(shí)例T方法集中如果某類型實(shí)現(xiàn)接口的方法的receiver是(TType)類型的,那么值類型的實(shí)例T和指針類型的實(shí)例*T都算實(shí)現(xiàn)了這個(gè)接口,因?yàn)檫@個(gè)方法既在值類型的實(shí)例T方法集中,也在指針類型的實(shí)例*T方法集中

舉個(gè)例子。接口方法Area(),自定義類型Circle有一個(gè)receiver類型為(c*Circle)的Area()方法時(shí),說明實(shí)現(xiàn)了接口的方法,但只有Circle實(shí)例的類型為指針類型時(shí),這個(gè)實(shí)例才算是實(shí)現(xiàn)了接口,才能賦值給接口實(shí)例,才能當(dāng)作一個(gè)接口參數(shù)。如下:

packagemain

import"fmt"

//Shaper接口類型

typeShaperinterface{

Area()float64

//Circlestruct類型

typeCirclestruct{

radiusfloat64

//Circle類型實(shí)現(xiàn)Shaper中的方法Area()

//receiver類型為指針類型

func(c*Circle)Area()float64{

return3.14*c.radius*c.radius

funcmain(){

//聲明2個(gè)接口實(shí)例

varins1,ins2Shaper

//Circle的指針類型實(shí)例

c1:=new(Circle)

c1.radius=2.5

ins1=c1

fmt.Println(ins1.Area())

//Circle的值類型實(shí)例

c2:=Circle{3.0}

//下面的將報(bào)錯(cuò)

ins2=c2

fmt.Println(ins2.Area())

報(bào)錯(cuò)結(jié)果:

cannotusec2(typeCircle)astypeShaper

inassignment:

CircledoesnotimplementShaper(Areamethodhas

pointerreceiver)

它的意思是,Circle值類型的實(shí)例c2沒有實(shí)現(xiàn)Share接口的Area()方法,它的Area()方法是指針類型的receiver。換句話說,值類型的c2實(shí)例的MethodSet中沒有receiver類型為指針的Area()方法。

所以,上面應(yīng)該改成:

ins2=c2

再聲明一個(gè)方法,它的receiver是值類型的。下面的代碼一切正常。

typeSquarestruct{

lengthfloat64

//實(shí)現(xiàn)方法Area(),receiver為值類型

func(sSquare)Area()float64{

returns.length*s.length

funcmain(){

varins3,ins4Shaper

//值類型的Square實(shí)例s1

s1:=Square{3.0}

ins3=s1

fmt.Println(ins3.Area())

//指針類型的Square實(shí)例s2

s2:=new(Square)

s2.length=4.0

ins4=s2

fmt.Println(ins4.Area())

所以,從struct類型定義的方法的角度去看,如果這個(gè)類型的方法有指針類型的receiver方法,則只能使用指針類型的實(shí)例賦值給接口變量,才算是實(shí)現(xiàn)了接口。如果這個(gè)類型的方法全是值類型的receiver方法,則可以隨意使用值類型或指針類型的實(shí)例賦值給接口變量。下面這兩個(gè)對應(yīng)關(guān)系,對于理解很有幫助:

實(shí)例的類型receiver

--------------------------------------

值類型:T(TType)

指針類型:*T(TType)或(T*Type)

receiver實(shí)例的類型

---------------------------

(TType)T或*T

(T*Type)*T

很經(jīng)常的,我們會(huì)直接使用推斷類型的賦值方式(如ins2:=c2)將實(shí)例賦值給一個(gè)變量,我們以為這個(gè)變量是接口的實(shí)例,但實(shí)際上并不一定。正如上面值類型的c2賦值給ins2,這個(gè)ins2將是從c2數(shù)據(jù)結(jié)構(gòu)拷貝而來的另一個(gè)副本數(shù)據(jù)結(jié)構(gòu),并非接口實(shí)例,但這時(shí)通過ins2也能調(diào)用Area()方法:

c2=Circle{3.2}

ins2:=c2

fmt.Println(ins2.Area())//正常執(zhí)行

之所以能調(diào)用,是因?yàn)镃ircle類型中有Area()方法,但這不是通過接口去調(diào)用的。

所以,在使用接口的時(shí)候,應(yīng)當(dāng)盡量使用var先聲明接口類型的實(shí)例,再將類型的實(shí)例賦值給接口實(shí)例(如varins1,ins2Shaper),或者使用ins1:=Shaper(c1)的方式。這樣,如果賦值給接口實(shí)例的類型實(shí)例沒有實(shí)現(xiàn)該接口,將會(huì)報(bào)錯(cuò)。

但是,為什么要限制指針類型的receiver只能是指針類型的實(shí)例的MethodSet呢?

看下圖,假如指針類型的receiver可以組成值類型實(shí)例的MethodSet,那么接口實(shí)例的第二個(gè)指針就必須找到值類型的實(shí)例的地址。但實(shí)際上,并非所有值類型的實(shí)例都能獲取到它們的地址。

哪些值類型的實(shí)例找不到地址?最常見的是那些簡單數(shù)據(jù)類型的別名類型,如果匿名生成它們的實(shí)例,它們的地址就會(huì)被Go徹底隱藏,外界找不到這個(gè)實(shí)例的地址。

例如:

packagemain

import"fmt"

typemyintint

func(m*myint)add()myint{

return*m+1

funcmain(){

fmt.Println(myint(3).add())

以下是報(bào)錯(cuò)信息:找不到myint(3)的地址

abc\abc.go:11:22:cannotcallpointermethodonmyint(3)

abc\abc.go:11:22:cannottaketheaddressofmyint(3)

這里的myint(3)是匿名的myint實(shí)例,它的底層是簡單數(shù)據(jù)類型int,myint(3)的地址會(huì)被徹底隱藏,只會(huì)提供它的值對象3。

普通方法和實(shí)現(xiàn)接口方法的區(qū)別

對于普通方法,無論是值類型還是指針類型的實(shí)例,都能正常調(diào)用,且調(diào)用時(shí)拷貝的內(nèi)容都由receiver的類型決定。

func(TType)method1//值類型receiver

func(T*Type)method2//指針類型receiver

指針類型的receiver決定了無論是值類型還是指針類型的實(shí)例,都拷貝實(shí)例的指針。值類型的receiver決定了無論是值類型還是指針類型的實(shí)例,都拷貝實(shí)例本身。

所以,對于person數(shù)據(jù)結(jié)構(gòu):

typepersonstruct{}

p1:=person{}//值類型的實(shí)例

p2:=new(person)//指針類型的實(shí)例

p1.method1()和p2.method1()都是拷貝整個(gè)person實(shí)例,只不過Go對待p2.method1()時(shí)多一個(gè)步驟:將其解除引用。所以p2.method1()等價(jià)于(*p2).method1()。

p1.method2()和p2.method2()都拷貝person實(shí)例的指針,只不過Go對待p1.method2()時(shí)多一個(gè)步驟:創(chuàng)建一個(gè)額外的引用。所以,p1.method2()等價(jià)于(p1).method2()。

而類型實(shí)現(xiàn)接口方法時(shí),methodset規(guī)則決定了類型實(shí)例是否實(shí)現(xiàn)了接口。

receiver實(shí)例的類型

---------------------------

(TType)T或*T

(T*Type)*T

對于接口abc、接口方法method1()、method2()和結(jié)構(gòu)person:

typeabcinterface{

method1

method2

typepersonstruct{}

func(Tperson)method1//值類型receiver

func(T*person)method2//指針類型receiver

p1:=abc(person)//接口變量保存值類型實(shí)例

p2:=abc(person)//接口變量保存指針類型實(shí)例

p2.method1()、p2.method2()以及p1.method1()都是允許的,都會(huì)通過接口實(shí)例去調(diào)用具體person實(shí)例的方法。

但p1.method2()是錯(cuò)誤的,因?yàn)閙ethod2()的receiver是指針類型的,導(dǎo)致p1沒有實(shí)現(xiàn)接口abc的method2()方法。

接口類型作為參數(shù)

將接口類型作為參數(shù)很常見。這時(shí),那些實(shí)現(xiàn)接口的實(shí)例都能作為接口類型參數(shù)傳遞給函數(shù)/方法。

例如,下面的myArea()函數(shù)的參數(shù)是nShaper,是接口類型。

packagemain

import(

"fmt"

//Shaper接口類型

typeShaperinterface{

Area()float64

//Circlestruct類型

typeCirclestruct{

radiusfloat64

//Circle類型實(shí)現(xiàn)Shaper中的方法Area()

func(c*Circle)Area()float64{

return3.14*c.radius*c.radius

funcmain(){

//Circle的指針類型實(shí)例

c1:=new(Circle)

c1.radius=2.5

myArea(c1)

funcmyArea(nShaper){

fmt.Println(n.Area())

上面myArea(c1)是將c

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論