在golang中,介面是一種類型,是用來將對方法進行一個收束,其作用是:1、作為方法的收束器,進行物件導向設計;2、作為各種資料的承載者,可以用來接收函數參數等。介面的定義語法「type 介面類型名稱interface{方法名( 參數清單1 ) 傳回值清單}」;當方法名稱首字母是大寫且這個介面類型名稱首字母也是大寫時,這個方法可以被介面所在的套件(package)以外的程式碼存取。
本教學操作環境:windows7系統、GO 1.18版本、Dell G3電腦。
一、介面(interface)是什麼
interface是一組method簽章的組合,我們透過interface定義物件的一組行為。
(注意method 和普通func的區別)
Interface是一種類型,和往常語言的介面不一樣,它只是用來將對方法進行一個收束。然而正是這種收束,使GO語言擁有了基於功能的物件導向。
介面的主要功能:
1.作為方法的收束器,進行物件導向設計。
2.作為各種資料的承載者,可以用來接收函數參數等。
這也是,GO語言提倡面向介面程式設計。
二、介面的定義使用
2.1定義
類似結構體
type 接口类型名 interface{ 方法名1( 参数列表1 ) 返回值列表1 方法名2( 参数列表2 ) 返回值列表2 … }
當然這只是有方法的介面定義,面向資料的介面不用。
介面名稱:使用type將介面定義為自訂的類型名稱。 Go語言的介面在命名時,通常會在單字後面加上er,如有寫入運算的介面叫Writer,有字串功能的介面叫Stringer等。介面名最好要能突顯該介面的型別意義。
方法名稱:當方法名稱首字母是大寫且這個介面類型名稱首字母也是大寫時,這個方法可以被介面所在的套件(package)以外的程式碼存取。
參數清單、傳回值清單:參數清單和傳回值清單中的參數變數名稱可以省略
2.2使用
一個物件只要全部實作了介面中的方法,那就實作了這個介面。換句話說,介面就是一個需要實作的方法清單。
//定义接口 type FastfoodStore interface{ MakeHamberger() MakeFriedChips() MakeSoftDrink() } //定义结构体 type KFC struct{} type HambergerKing struct{} //实现了接口中所有的方法 func (kfc KFC) MakeHamberger(){ fmt.println("肯德基的汉堡") } func (kfc KFC) MakeFriedChips(){ fmt.println("肯德基的薯条") } func (kfc KFC) MakeSoftDrink(){ fmt.println("肯德基的饮料") } func (K *HambergerKing) MakeHameberger(){ fmt.println("汉堡王的汉堡") } func (K *HambergerKing) MakeFriedChips(){ fmt.println("汉堡王的薯条") } func (K *HambergerKing) MakeSoftDrink(){ fmt.println("汉堡王的饮料") }
我們可以看到不同於Java的介面明確實現,Go的語言是隱式實現的。
- 在Java 中:實作介面需要明確地宣告介面並實作所有方法;
- 在Go 中:實作介面的所有方法都隱含地實作了介面;
那麼GO語言是如何檢查該類型是否是介面呢?
答案:Go 語言只會在傳遞參數、傳回參數、變數賦值時才會對某個型別是否實作介面進行檢查。從類型檢查的過程來看,編譯器僅在需要時才檢查類型,類型實作介面時才需要實作介面中的全部方法,不需要像 Java 等程式語言一樣明確宣告。
我們可以看到在上面實作介面的時候,KFC是用結構體物件實現的,而Hamberger king是透過指標實現的兩者有什麼不同呢?
答案:差別在於我們初始化介面的時候
//结构体初始化和指针初始化 var f faststore = KFC{} //可以通过编译 var f faststore = &KFC{} //可以通过编译 var f faststore = HambergerKing{} //无法通过编译 var f faststore = &HambergerKing{} //可以通过编译
所以在我們使用指標進行實現,結構體初始化時,為啥不行呢?
答:Go 語言在傳遞參數時都是傳值的。
如上圖所示,無論上述程式碼中初始化的變數指標或結構體,使用 呼叫方法時都會發生值拷貝:
如上图左侧,对于 &HambergerKing{} 来说,这意味着拷贝一个新的 &HambergerKing{} 指针,这个指针与原来的指针指向一个相同并且唯一的结构体,所以编译器可以隐式的对变量解引用(dereference)获取指针指向的结构体;
如上图右侧,对于 HambergerKing{} 来说,这意味着方法会接受一个全新的 HambergerKing{},因为方法的参数是*HambergerKing,编译器不会无中生有创建一个新的指针;即使编译器可以创建新指针,这个指针指向的也不是最初调用该方法的结构体;
上面的分析解释了指针类型的现象,当我们使用指针实现接口时,只有指针类型的变量才会实现该接口;当我们使用结构体实现接口时,指针类型和结构体类型都会实现该接口。当然这并不意味着我们应该一律使用结构体实现接口,这个问题在实际工程中也没那么重要,在这里我们只想解释现象背后的原因。
在上面我们说过,interface有两种用法,现在介绍了其中一种就是作为方法的收束器。那么第二种就是作为数据的承载者。
2.3 数据承载者
作为数据容器时,接口就是一个“空”接口,这个空来形容没有Method。空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
需要注意的是,与 C 语言中的 void * 不同,interface{} 类型不是任意类型。如果我们将类型转换成了 interface{} 类型,变量在运行期间的类型也会发生变化,获取变量类型时会得到 interface{}。
我们尝试从底层实现来解释两种用法的不同,你会好理解一些。Go 语言使用 runtime.iface 表示第一种接口,使用 runtime.eface 表示第二种不包含任何方法的接口 interface{},两种接口虽然都使用 interface 声明,但是由于后者在 Go 语言中很常见,所以在实现时使用了特殊的类型。
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
// 空接口作为函数参数 func show(a interface{}) { fmt.Printf("type:%T value:%v\n", a, a) }
空接口作为map的值
使用空接口实现可以保存任意值的字典。
// 空接口作为map值 var studentInfo = make(map[string]interface{}) studentInfo["name"] = "Wilen" studentInfo["age"] = 18 studentInfo["married"] = false fmt.Println(studentInfo) //gin框架的gin.H{}
三、关于接口类型转换
interface 可以存储所有的值,那么自然会涉及到类型转换这个话题。与此同时,我们也将在这节细说类型转换中,因为结构体实现和结构体指针实现的接口的异同。
3.1结构体指针实现接口
//我们仍然运用上面快餐店的例子 type Store interface{ MakeHamberger() } type KFC struct{ name string } func (k *KFC) MakeHamberger(){ fmt.println(k.name+"制作了一个汉堡") } func main(){ var s store = &KFC{name:"东街店"} store.MakeHamberger() }
这里将上述代码生成的汇编指令拆分成三部分分析:
1.结构体 KFC 的初始化;
KFC的初始化又可以分为下面几步:
获取 KFC 结构体类型指针并将其作为参数放到栈上;
通过 CALL 指定调用 runtime.newobject函数,这个函数会以 KFC 结构体类型指针作为入参,分配一片新的内存空间并将指向这片内存空间的指针返回到 SP+8 上;
SP+8 现在存储了一个指向 KFC 结构体的指针,我们将栈上的指针拷贝到寄存器 DI 上方便操作;
由于 Cat 中只包含一个字符串类型的 Name 变量,所以在这里会分别将字符串地址 &"东街店" 和字符串长度 6 设置到结构体上。
2.赋值触发的类型转换过程;
因为 KFC 结构体的定义中只包含一个字符串,而字符串在 Go 语言中总共占 16 字节,所以每一个 KFC 结构体的大小都是 16 字节。初始化 KFC 结构体之后就进入了将 *KFC 转换成 Store 类型的过程了:
类型转换的过程比较简单,Store 作为一个包含方法的接口,它在底层使用 [runtime.iface] 结构体表示。runtime.iface 结构体包含两个字段,其中一个是指向数据的指针,另一个是表示接口和结构体关系的 tab 字段,我们已经通过上一段代码 SP+8 初始化了 KFC 结构体指针,这段代码只是将编译期间生成的 runtime.itab 结构体指针复制到 SP 上:
到这里,我们会发现 SP ~ SP+16 共同组成了 runtime.iface 结构体。
3.调用接口的方法 Quack();
栈上的这个 runtime.iface 也是 MakeHamberger() 方法的第一个入参。通过CALL()完成方法的调用。
3.2 结构体实现接口
//我们仍然运用上面快餐店的例子 type Store interface{ MakeHamberger() } type KFC struct{ name string } func (k KFC) MakeHamberger(){ fmt.println(k.name+"制作了一个汉堡") } func main(){ var s store = KFC{name:"东街店"} store.MakeHamberger() }
如果我们在初始化变量时使用指针类型 &KFC{Name: "东街店"} 也能够通过编译,不过生成的汇编代码和上一节中的几乎完全相同,所以这里也就不分析这个情况了。
初始化 KFC 结构体;
在栈上初始化 KFC 结构体,而上一节的代码在堆上申请了 16 字节的内存空间,栈上只有一个指向 KFC 的指针。
完成从 KFC 到 Store 接口的类型转换;
初始化结构体后会进入类型转换的阶段,编译器会将 go.itab."".KFC,"".Store 的地址和指向 KFC 结构体的指针作为参数一并传入 runtime.convT2I 函数:这个函数会获取 runtime.itab 中存储的类型,根据类型的大小申请一片内存空间并将 elem 指针中的内容拷贝到目标的内存中:
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 会返回一个 runtime.iface,其中包含 runtime.itab 指针和 KFC 变量。当前函数返回之后,main 函数的栈上会包含以下数据:
SP 和 SP+8 中存储的 runtime.itab 和 KFC 指针是 runtime.convT2I 函数的入参,这个函数的返回值位于 SP+16,是一个占 16 字节内存空间的 runtime.iface 结构体,SP+32 存储的是在栈上的 KFC 结构体,它会在 runtime.convT2I 执行的过程中拷贝到堆上。
3.3类型断言
如何将一个接口类型转换成具体类型?
x.(T)
非空接口
func main() { var c Store = &KFC{Name: "东街店"} switch c.(type) { case *KFC: kfc := c.(*KFC) kfc.MakeHamberger() } }
因为 Go 语言的编译器做了一些优化,所以代码中没有runtime.iface 的构建过程,不过对于这一节要介绍的类型断言和转换没有太多的影响。
switch语句生成的汇编指令会将目标类型的 hash 与接口变量中的 itab.hash 进行比较
空接口
func main() { var c interface{} = &KFC{Name: "东街店"} switch c.(type) { case *KFC: kfc := c.(*KFC) kfc.MakeHamberger() } }
上述代码会在类型断言时就不是直接获取变量中具体类型的 runtime._type,而是从 eface._type 中获取,汇编指令仍然会使用目标类型的 hash 与变量的类型比较.
以上是golang的介面有啥用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

golangisidealforperformance-Critical-clitageAppations and ConcurrentPrompromming,而毛皮刺激性,快速播種和可及性。 1)forhigh-porformanceneeds,pelectgolangduetoitsefefsefefseffifeficefsefeflicefsiveficefsiveandconcurrencyfeatures.2)fordataa-fordataa-fordata-fordata-driventriventriventriventriventrivendissp pynonnononesp

Golang通過goroutine和channel實現高效並發:1.goroutine是輕量級線程,使用go關鍵字啟動;2.channel用於goroutine間安全通信,避免競態條件;3.使用示例展示了基本和高級用法;4.常見錯誤包括死鎖和數據競爭,可用gorun-race檢測;5.性能優化建議減少channel使用,合理設置goroutine數量,使用sync.Pool管理內存。

Golang更適合系統編程和高並發應用,Python更適合數據科學和快速開發。 1)Golang由Google開發,靜態類型,強調簡潔性和高效性,適合高並發場景。 2)Python由GuidovanRossum創造,動態類型,語法簡潔,應用廣泛,適合初學者和數據處理。

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

Go語言在並發編程、性能、學習曲線等方面有獨特優勢:1.並發編程通過goroutine和channel實現,輕量高效。 2.編譯速度快,運行性能接近C語言。 3.語法簡潔,學習曲線平緩,生態系統豐富。

Golang和Python的主要區別在於並發模型、類型系統、性能和執行速度。 1.Golang使用CSP模型,適用於高並發任務;Python依賴多線程和GIL,適合I/O密集型任務。 2.Golang是靜態類型,Python是動態類型。 3.Golang編譯型語言執行速度快,Python解釋型語言開發速度快。

Golang通常比C 慢,但Golang在並發編程和開發效率上更具優勢:1)Golang的垃圾回收和並發模型使其在高並發場景下表現出色;2)C 通過手動內存管理和硬件優化獲得更高性能,但開發複雜度較高。

Golang在雲計算和DevOps中的應用廣泛,其優勢在於簡單性、高效性和並發編程能力。 1)在雲計算中,Golang通過goroutine和channel機制高效處理並發請求。 2)在DevOps中,Golang的快速編譯和跨平台特性使其成為自動化工具的首選。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

禪工作室 13.0.1
強大的PHP整合開發環境

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具