Wozu dient die Golang-Schnittstelle?

青灯夜游
Freigeben: 2023-01-04 20:42:16
Original
3719 Leute haben es durchsucht

In Golang ist eine Schnittstelle ein Typ, der zum Verpacken von Methoden verwendet wird. Seine Funktionen sind: 1. Als Wrapper für Methoden für objektorientiertes Design 2. Als Träger verschiedener Daten, kann zum Empfangen von Funktionen verwendet werden Parameter usw. Die Definitionssyntax der Schnittstelle lautet „Typschnittstellentypname Schnittstelle {Methodenname (Parameterliste 1) Rückgabewertliste}“, wenn der erste Buchstabe des Methodennamens großgeschrieben wird und der erste Buchstabe des Schnittstellentypnamens ebenfalls großgeschrieben wird. Diese Methode kann von dem Paket verwendet werden, in dem sich die Schnittstelle befindet (Paket), außerhalb des Codezugriffs.

Wozu dient die Golang-Schnittstelle?

Die Betriebsumgebung dieses Tutorials: Windows 7-System, GO Version 1.18, Dell G3-Computer.

1. Was ist eine Schnittstelle?

Schnittstelle ist eine Kombination von Methodensignaturen. Wir verwenden eine Schnittstelle, um eine Reihe von Verhaltensweisen eines Objekts zu definieren.

(Beachten Sie den Unterschied zwischen Methode und gewöhnlicher Funktion)

Schnittstelle ist ein Typ, der sich von der Schnittstelle gewöhnlicher Sprachen unterscheidet. Er wird nur zum Verpacken von Methoden verwendet. Es ist jedoch diese Art der Konvergenz, die der GO-Sprache eine funktionsbasierte Objektorientierung verleiht.

Die Hauptfunktionen der Schnittstelle:

1. Als Methodenkonverger für objektorientiertes Design.

2. Als Träger verschiedener Daten kann es zum Empfang von Funktionsparametern usw. verwendet werden.

Dies ist auch der Fall, denn die GO-Sprache befürwortet die schnittstellenorientierte Programmierung.

2. Definition der Schnittstelle mit

2.1 Definition

Ähnliche Struktur

type 接口类型名 interface{
    方法名1( 参数列表1 ) 返回值列表1
    方法名2( 参数列表2 ) 返回值列表2
    …
}
Nach dem Login kopieren

Natürlich handelt es sich hierbei nur um eine Schnittstellendefinition mit Methoden, die nicht für datenorientierte Schnittstellen verwendet wird.

  • Schnittstellenname: Verwenden Sie Typ, um die Schnittstelle als benutzerdefinierten Typnamen zu definieren. Bei der Benennung von Go-Sprachschnittstellen wird normalerweise er nach dem Wort hinzugefügt. Beispielsweise heißt die Schnittstelle mit Schreiboperationen Writer und die Schnittstelle mit String-Funktion heißt Stringer usw. Am besten ist es, wenn der Schnittstellenname die Typbedeutung der Schnittstelle hervorhebt.

  • Methodenname: Wenn der erste Buchstabe des Methodennamens großgeschrieben wird und der erste Buchstabe des Schnittstellentypnamens ebenfalls großgeschrieben wird, kann auf diese Methode durch Code außerhalb des Pakets zugegriffen werden, in dem sich die Schnittstelle befindet.

  • Parameterliste, Rückgabewertliste: Die Parametervariablennamen in der Parameterliste und der Rückgabewertliste können weggelassen werden

2.2 Verwenden von

Solange ein Objekt alle Methoden in der Schnittstelle implementiert , dann wird diese Schnittstelle implementiert. Mit anderen Worten, eine Schnittstelle ist eine Liste von Methoden, die implementiert werden müssen.

//定义接口
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("汉堡王的饮料")
}
Nach dem Login kopieren

Wir können sehen, dass die Sprache von Go im Gegensatz zur Java-Schnittstelle, die explizit implementiert ist, implizit implementiert ist.

  • In Java: Um eine Schnittstelle zu implementieren, müssen Sie die Schnittstelle explizit deklarieren und alle Methoden implementieren.
  • In Go: Alle Methoden, die die Schnittstelle implementieren, implementieren die Schnittstelle implizit.

Wie funktioniert die Go-Sprache? check Handelt es sich bei diesem Typ um eine Schnittstelle?

Antwort: Die Go-Sprache prüft nur, ob ein Typ die Schnittstelle implementiert, wenn Parameter übergeben, Parameter zurückgegeben und Variablen zugewiesen werden. Aus Sicht des Typprüfungsprozesses überprüft der Compiler den Typ nur bei Bedarf. Wenn ein Typ eine Schnittstelle implementiert, muss er nur alle Methoden in der Schnittstelle implementieren und muss nicht wie in Programmiersprachen explizit deklariert werden ​wie Java.

Wir können sehen, dass bei der Implementierung der obigen Schnittstelle KFC mithilfe von Strukturobjekten implementiert wird, während Hamberger King mithilfe von Zeigern implementiert wird. Was ist der Unterschied zwischen den beiden?

Antwort: Der Unterschied besteht darin, wann wir die Schnittstelle initialisieren

//结构体初始化和指针初始化
var f faststore = KFC{}             //可以通过编译
var f faststore = &KFC{}            //可以通过编译

var f faststore = HambergerKing{}    //无法通过编译
var f faststore = &HambergerKing{}    //可以通过编译
Nach dem Login kopieren

Warum funktioniert es also nicht, wenn wir Zeiger verwenden, um die Struktur zu implementieren und zu initialisieren?

Antwort: Go-Sprache übergibt Parameter nach Wert.

Wozu dient die Golang-Schnittstelle?

Wie im Bild oben gezeigt, erfolgt unabhängig vom Variablenzeiger oder der im obigen Code initialisierten Struktur das Kopieren von Werten beim Aufruf der Methode:

如上图左侧,对于 &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 语言中很常见,所以在实现时使用了特殊的类型。

Wozu dient die Golang-Schnittstelle?

空接口作为函数的参数

使用空接口实现可以接收任意类型的函数参数。

// 空接口作为函数参数
func show(a interface{}) {
    fmt.Printf("type:%T value:%v\n", a, a)
}
Nach dem Login kopieren

空接口作为map的值

使用空接口实现可以保存任意值的字典。

// 空接口作为map值
    var studentInfo = make(map[string]interface{})
    studentInfo["name"] = "Wilen"
    studentInfo["age"] = 18
    studentInfo["married"] = false
    fmt.Println(studentInfo)
//gin框架的gin.H{}
Nach dem Login kopieren

三、关于接口类型转换

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()
}
Nach dem Login kopieren

这里将上述代码生成的汇编指令拆分成三部分分析:

1.结构体 KFC 的初始化;

KFC的初始化又可以分为下面几步:

  • 获取 KFC 结构体类型指针并将其作为参数放到栈上;

  • 通过 CALL 指定调用 runtime.newobject函数,这个函数会以 KFC 结构体类型指针作为入参,分配一片新的内存空间并将指向这片内存空间的指针返回到 SP+8 上;

  • SP+8 现在存储了一个指向 KFC 结构体的指针,我们将栈上的指针拷贝到寄存器 DI 上方便操作;

  • 由于 Cat 中只包含一个字符串类型的 Name 变量,所以在这里会分别将字符串地址 &"东街店" 和字符串长度 6 设置到结构体上。

Wozu dient die Golang-Schnittstelle?

2.赋值触发的类型转换过程;

因为 KFC 结构体的定义中只包含一个字符串,而字符串在 Go 语言中总共占 16 字节,所以每一个 KFC 结构体的大小都是 16 字节。初始化 KFC 结构体之后就进入了将 *KFC 转换成 Store 类型的过程了:

类型转换的过程比较简单,Store 作为一个包含方法的接口,它在底层使用 [runtime.iface] 结构体表示。runtime.iface 结构体包含两个字段,其中一个是指向数据的指针,另一个是表示接口和结构体关系的 tab 字段,我们已经通过上一段代码 SP+8 初始化了 KFC 结构体指针,这段代码只是将编译期间生成的 runtime.itab 结构体指针复制到 SP 上:

Wozu dient die Golang-Schnittstelle?

到这里,我们会发现 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()
}
Nach dem Login kopieren

如果我们在初始化变量时使用指针类型 &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
}
Nach dem Login kopieren

runtime.convT2I 会返回一个 runtime.iface,其中包含 runtime.itab 指针和 KFC 变量。当前函数返回之后,main 函数的栈上会包含以下数据:

Wozu dient die Golang-Schnittstelle?

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()
    }
}
Nach dem Login kopieren

因为 Go 语言的编译器做了一些优化,所以代码中没有runtime.iface 的构建过程,不过对于这一节要介绍的类型断言和转换没有太多的影响。

switch语句生成的汇编指令会将目标类型的 hash 与接口变量中的 itab.hash 进行比较

空接口

func main() {
    var c interface{} = &KFC{Name: "东街店"}
    switch c.(type) {
    case *KFC:
        kfc := c.(*KFC)
        kfc.MakeHamberger()
    }
}
Nach dem Login kopieren

上述代码会在类型断言时就不是直接获取变量中具体类型的 runtime._type,而是从 eface._type 中获取,汇编指令仍然会使用目标类型的 hash 与变量的类型比较.

【相关推荐:Go视频教程编程教学

Das obige ist der detaillierte Inhalt vonWozu dient die Golang-Schnittstelle?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage