這篇文章主要講解了“golang的接口怎么定義使用”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“golang的接口怎么定義使用”吧!
創(chuàng)新互聯(lián)公司專注于網(wǎng)站制作、成都網(wǎng)站制作、網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站制作、網(wǎng)站開發(fā)。公司秉持“客戶至上,用心服務(wù)”的宗旨,從客戶的利益和觀點(diǎn)出發(fā),讓客戶在網(wǎng)絡(luò)營(yíng)銷中找到自己的駐足之地。尊重和關(guān)懷每一位客戶,用嚴(yán)謹(jǐn)?shù)膽B(tài)度對(duì)待客戶,用專業(yè)的服務(wù)創(chuàng)造價(jià)值,成為客戶值得信賴的朋友,為客戶解除后顧之憂。
在golang中,接口是一種類型,是用來將對(duì)方法進(jìn)行一個(gè)收束,其作用是:1、作為方法的收束器,進(jìn)行面向?qū)ο笤O(shè)計(jì);2、作為各種數(shù)據(jù)的承載者,可以用來接收函數(shù)參數(shù)等。接口的定義語法“type 接口類型名 interface{方法名( 參數(shù)列表1 ) 返回值列表}”;當(dāng)方法名首字母是大寫且這個(gè)接口類型名首字母也是大寫時(shí),這個(gè)方法可以被接口所在的包(package)之外的代碼訪問。
interface是一組method簽名的組合,我們通過interface來定義對(duì)象的一組行為。
(注意method 和普通func的區(qū)別)
Interface是一種類型,和往常語言的接口不一樣,它只是用來將對(duì)方法進(jìn)行一個(gè)收束。然而正是這種收束,使GO語言擁有了基于功能的面向?qū)ο蟆?/p>
接口的主要功能:
1.作為方法的收束器,進(jìn)行面向?qū)ο笤O(shè)計(jì)。
2.作為各種數(shù)據(jù)的承載者,可以用來接收函數(shù)參數(shù)等。
這也是,GO語言提倡面向接口編程。
2.1定義
類似結(jié)構(gòu)體
type 接口類型名 interface{
方法名1( 參數(shù)列表1 ) 返回值列表1
方法名2( 參數(shù)列表2 ) 返回值列表2
…
}
當(dāng)然這只是有方法的接口定義,面向數(shù)據(jù)的接口不用。
接口名:使用type將接口定義為自定義的類型名。Go語言的接口在命名時(shí),一般會(huì)在單詞后面添加er,如有寫操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出該接口的類型含義。
方法名:當(dāng)方法名首字母是大寫且這個(gè)接口類型名首字母也是大寫時(shí),這個(gè)方法可以被接口所在的包(package)之外的代碼訪問。
參數(shù)列表、返回值列表:參數(shù)列表和返回值列表中的參數(shù)變量名可以省略
2.2使用
一個(gè)對(duì)象只要全部實(shí)現(xiàn)了接口中的方法,那么就實(shí)現(xiàn)了這個(gè)接口。換句話說,接口就是一個(gè)需要實(shí)現(xiàn)的方法列表。
//定義接口
type FastfoodStore interface{
MakeHamberger()
MakeFriedChips()
MakeSoftDrink()
}
//定義結(jié)構(gòu)體
type KFC struct{}
type HambergerKing struct{}
//實(shí)現(xiàn)了接口中所有的方法
func (kfc KFC) MakeHamberger(){
fmt.println("肯德基的漢堡")
}
func (kfc KFC) MakeFriedChips(){
fmt.println("肯德基的薯?xiàng)l")
}
func (kfc KFC) MakeSoftDrink(){
fmt.println("肯德基的飲料")
}
func (K *HambergerKing) MakeHameberger(){
fmt.println("漢堡王的漢堡")
}
func (K *HambergerKing) MakeFriedChips(){
fmt.println("漢堡王的薯?xiàng)l")
}
func (K *HambergerKing) MakeSoftDrink(){
fmt.println("漢堡王的飲料")
}
我們可以看到不同于Java的接口顯式實(shí)現(xiàn),Go的語言是隱式實(shí)現(xiàn)的。
在 Java 中:實(shí)現(xiàn)接口需要顯式地聲明接口并實(shí)現(xiàn)所有方法;
在 Go 中:實(shí)現(xiàn)接口的所有方法就隱式地實(shí)現(xiàn)了接口;
那么GO語言是如何檢查該類型是否是接口呢?
答:Go 語言只會(huì)在傳遞參數(shù)、返回參數(shù)以及變量賦值時(shí)才會(huì)對(duì)某個(gè)類型是否實(shí)現(xiàn)接口進(jìn)行檢查。從類型檢查的過程來看,編譯器僅在需要時(shí)才檢查類型,類型實(shí)現(xiàn)接口時(shí)只需要實(shí)現(xiàn)接口中的全部方法,不需要像 Java 等編程語言中一樣顯式聲明。
我們可以看到在上面實(shí)現(xiàn)接口的時(shí)候,KFC是用結(jié)構(gòu)體對(duì)象實(shí)現(xiàn)的,而Hamberger king是通過指針實(shí)現(xiàn)的兩者有什么不同呢?
答:區(qū)別在于我們初始化接口的時(shí)候
//結(jié)構(gòu)體初始化和指針初始化
var f faststore = KFC{} //可以通過編譯
var f faststore = &KFC{} //可以通過編譯
var f faststore = HambergerKing{} //無法通過編譯
var f faststore = &HambergerKing{} //可以通過編譯
所以在我們使用指針進(jìn)行實(shí)現(xiàn),結(jié)構(gòu)體初始化時(shí),為啥不行呢?
答:Go 語言在傳遞參數(shù)時(shí)都是傳值的。
如上圖所示,無論上述代碼中初始化的變量指針還是結(jié)構(gòu)體,使用 調(diào)用方法時(shí)都會(huì)發(fā)生值拷貝:
如上圖左側(cè),對(duì)于 &HambergerKing{} 來說,這意味著拷貝一個(gè)新的 &HambergerKing{} 指針,這個(gè)指針與原來的指針指向一個(gè)相同并且唯一的結(jié)構(gòu)體,所以編譯器可以隱式的對(duì)變量解引用(dereference)獲取指針指向的結(jié)構(gòu)體;
如上圖右側(cè),對(duì)于 HambergerKing{} 來說,這意味著方法會(huì)接受一個(gè)全新的 HambergerKing{},因?yàn)榉椒ǖ膮?shù)是*HambergerKing,編譯器不會(huì)無中生有創(chuàng)建一個(gè)新的指針;即使編譯器可以創(chuàng)建新指針,這個(gè)指針指向的也不是最初調(diào)用該方法的結(jié)構(gòu)體;
上面的分析解釋了指針類型的現(xiàn)象,當(dāng)我們使用指針實(shí)現(xiàn)接口時(shí),只有指針類型的變量才會(huì)實(shí)現(xiàn)該接口;當(dāng)我們使用結(jié)構(gòu)體實(shí)現(xiàn)接口時(shí),指針類型和結(jié)構(gòu)體類型都會(huì)實(shí)現(xiàn)該接口。當(dāng)然這并不意味著我們應(yīng)該一律使用結(jié)構(gòu)體實(shí)現(xiàn)接口,這個(gè)問題在實(shí)際工程中也沒那么重要,在這里我們只想解釋現(xiàn)象背后的原因。
在上面我們說過,interface有兩種用法,現(xiàn)在介紹了其中一種就是作為方法的收束器。那么第二種就是作為數(shù)據(jù)的承載者。
2.3 數(shù)據(jù)承載者
作為數(shù)據(jù)容器時(shí),接口就是一個(gè)“空”接口,這個(gè)空來形容沒有Method??読nterface(interface{})不包含任何的method,正因?yàn)槿绱?,所有的類型都?shí)現(xiàn)了空interface??読nterface對(duì)于描述起不到任何的作用(因?yàn)樗话魏蔚膍ethod),但是空interface在我們需要存儲(chǔ)任意類型的數(shù)值的時(shí)候相當(dāng)有用,因?yàn)樗梢源鎯?chǔ)任意類型的數(shù)值。它有點(diǎn)類似于C語言的void*類型。
需要注意的是,與 C 語言中的 void * 不同,interface{} 類型不是任意類型。如果我們將類型轉(zhuǎn)換成了 interface{} 類型,變量在運(yùn)行期間的類型也會(huì)發(fā)生變化,獲取變量類型時(shí)會(huì)得到 interface{}。
我們嘗試從底層實(shí)現(xiàn)來解釋兩種用法的不同,你會(huì)好理解一些。Go 語言使用 runtime.iface 表示第一種接口,使用 runtime.eface 表示第二種不包含任何方法的接口 interface{},兩種接口雖然都使用 interface 聲明,但是由于后者在 Go 語言中很常見,所以在實(shí)現(xiàn)時(shí)使用了特殊的類型。
空接口作為函數(shù)的參數(shù)
使用空接口實(shí)現(xiàn)可以接收任意類型的函數(shù)參數(shù)。
// 空接口作為函數(shù)參數(shù)
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
空接口作為map的值
使用空接口實(shí)現(xiàn)可以保存任意值的字典。
// 空接口作為map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "Wilen"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)
//gin框架的gin.H{}
interface 可以存儲(chǔ)所有的值,那么自然會(huì)涉及到類型轉(zhuǎn)換這個(gè)話題。與此同時(shí),我們也將在這節(jié)細(xì)說類型轉(zhuǎn)換中,因?yàn)榻Y(jié)構(gòu)體實(shí)現(xiàn)和結(jié)構(gòu)體指針實(shí)現(xiàn)的接口的異同。
3.1結(jié)構(gòu)體指針實(shí)現(xiàn)接口
//我們?nèi)匀贿\(yùn)用上面快餐店的例子
type Store interface{
MakeHamberger()
}
type KFC struct{
name string
}
func (k *KFC) MakeHamberger(){
fmt.println(k.name+"制作了一個(gè)漢堡")
}
func main(){
var s store = &KFC{name:"東街店"}
store.MakeHamberger()
}
這里將上述代碼生成的匯編指令拆分成三部分分析:
KFC的初始化又可以分為下面幾步:
獲取 KFC 結(jié)構(gòu)體類型指針并將其作為參數(shù)放到棧上;
通過 CALL 指定調(diào)用 runtime.newobject函數(shù),這個(gè)函數(shù)會(huì)以 KFC 結(jié)構(gòu)體類型指針作為入?yún)?,分配一片新的?nèi)存空間并將指向這片內(nèi)存空間的指針返回到 SP+8 上;
SP+8 現(xiàn)在存儲(chǔ)了一個(gè)指向 KFC 結(jié)構(gòu)體的指針,我們將棧上的指針拷貝到寄存器 DI 上方便操作;
由于 Cat 中只包含一個(gè)字符串類型的 Name 變量,所以在這里會(huì)分別將字符串地址 &"東街店" 和字符串長(zhǎng)度 6 設(shè)置到結(jié)構(gòu)體上。
因?yàn)?KFC 結(jié)構(gòu)體的定義中只包含一個(gè)字符串,而字符串在 Go 語言中總共占 16 字節(jié),所以每一個(gè) KFC 結(jié)構(gòu)體的大小都是 16 字節(jié)。初始化 KFC 結(jié)構(gòu)體之后就進(jìn)入了將 *KFC 轉(zhuǎn)換成 Store 類型的過程了:
類型轉(zhuǎn)換的過程比較簡(jiǎn)單,Store 作為一個(gè)包含方法的接口,它在底層使用 [runtime.iface] 結(jié)構(gòu)體表示。runtime.iface 結(jié)構(gòu)體包含兩個(gè)字段,其中一個(gè)是指向數(shù)據(jù)的指針,另一個(gè)是表示接口和結(jié)構(gòu)體關(guān)系的 tab 字段,我們已經(jīng)通過上一段代碼 SP+8 初始化了 KFC 結(jié)構(gòu)體指針,這段代碼只是將編譯期間生成的 runtime.itab 結(jié)構(gòu)體指針復(fù)制到 SP 上:
到這里,我們會(huì)發(fā)現(xiàn) SP ~ SP+16 共同組成了 runtime.iface 結(jié)構(gòu)體。
棧上的這個(gè) runtime.iface 也是 MakeHamberger() 方法的第一個(gè)入?yún)?。通過CALL()完成方法的調(diào)用。
3.2 結(jié)構(gòu)體實(shí)現(xiàn)接口
//我們?nèi)匀贿\(yùn)用上面快餐店的例子
type Store interface{
MakeHamberger()
}
type KFC struct{
name string
}
func (k KFC) MakeHamberger(){
fmt.println(k.name+"制作了一個(gè)漢堡")
}
func main(){
var s store = KFC{name:"東街店"}
store.MakeHamberger()
}
如果我們?cè)诔跏蓟兞繒r(shí)使用指針類型 &KFC{Name: "東街店"} 也能夠通過編譯,不過生成的匯編代碼和上一節(jié)中的幾乎完全相同,所以這里也就不分析這個(gè)情況了。
在棧上初始化 KFC 結(jié)構(gòu)體,而上一節(jié)的代碼在堆上申請(qǐng)了 16 字節(jié)的內(nèi)存空間,棧上只有一個(gè)指向 KFC 的指針。
初始化結(jié)構(gòu)體后會(huì)進(jìn)入類型轉(zhuǎn)換的階段,編譯器會(huì)將 go.itab."".KFC,"".Store 的地址和指向 KFC 結(jié)構(gòu)體的指針作為參數(shù)一并傳入 runtime.convT2I 函數(shù):這個(gè)函數(shù)會(huì)獲取 runtime.itab 中存儲(chǔ)的類型,根據(jù)類型的大小申請(qǐng)一片內(nèi)存空間并將 elem 指針中的內(nèi)容拷貝到目標(biāo)的內(nèi)存中:
func convT2I(tab *itab, elem unsafe.Pointer) (i iface) {
t := tab._type
x := mallocgc(t.size, t, true)
typedmemmove(t, x, elem)
i.tab = tab
i.data = x
return
}
runtime.convT2I 會(huì)返回一個(gè) runtime.iface,其中包含 runtime.itab 指針和 KFC 變量。當(dāng)前函數(shù)返回之后,main 函數(shù)的棧上會(huì)包含以下數(shù)據(jù):
SP 和 SP+8 中存儲(chǔ)的 runtime.itab 和 KFC 指針是 runtime.convT2I 函數(shù)的入?yún)?,這個(gè)函數(shù)的返回值位于 SP+16,是一個(gè)占 16 字節(jié)內(nèi)存空間的 runtime.iface 結(jié)構(gòu)體,SP+32 存儲(chǔ)的是在棧上的 KFC 結(jié)構(gòu)體,它會(huì)在 runtime.convT2I 執(zhí)行的過程中拷貝到堆上。
3.3類型斷言
如何將一個(gè)接口類型轉(zhuǎn)換成具體類型?
x.(T)
func main() {
var c Store = &KFC{Name: "東街店"}
switch c.(type) {
case *KFC:
kfc := c.(*KFC)
kfc.MakeHamberger()
}
}
因?yàn)?Go 語言的編譯器做了一些優(yōu)化,所以代碼中沒有runtime.iface 的構(gòu)建過程,不過對(duì)于這一節(jié)要介紹的類型斷言和轉(zhuǎn)換沒有太多的影響。
switch語句生成的匯編指令會(huì)將目標(biāo)類型的 hash 與接口變量中的 itab.hash 進(jìn)行比較
func main() {
var c interface{} = &KFC{Name: "東街店"}
switch c.(type) {
case *KFC:
kfc := c.(*KFC)
kfc.MakeHamberger()
}
}
上述代碼會(huì)在類型斷言時(shí)就不是直接獲取變量中具體類型的 runtime._type,而是從 eface._type 中獲取,匯編指令仍然會(huì)使用目標(biāo)類型的 hash 與變量的類型比較.
感謝各位的閱讀,以上就是“golang的接口怎么定義使用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)golang的接口怎么定義使用這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!