>  기사  >  백엔드 개발  >  golang의 인터페이스는 어떤 용도로 사용되나요?

golang의 인터페이스는 어떤 용도로 사용되나요?

青灯夜游
青灯夜游원래의
2023-01-04 20:42:163692검색

golang에서 인터페이스는 메소드를 마무리하는 데 사용되는 유형입니다. 1. 객체 지향 설계를 위한 메소드의 래퍼로서 2. 다양한 데이터의 전달자로 기능을 수신하는 데 사용할 수 있습니다. 매개변수 등 인터페이스의 정의 구문은 "유형 인터페이스 유형 이름 인터페이스{메소드 이름(매개변수 목록 1) 반환 값 목록}"입니다. 메소드 이름의 첫 글자도 대문자로 표시되며, 인터페이스 유형 이름의 첫 글자도 대문자로 표시됩니다. 이 방법은 코드 액세스 외부에 인터페이스가 있는 패키지(패키지)에서 사용할 수 있습니다.

golang의 인터페이스는 어떤 용도로 사용되나요?

이 튜토리얼의 운영 환경: Windows 7 시스템, GO 버전 1.18, Dell G3 컴퓨터.

1. 인터페이스란 무엇입니까?

인터페이스는 메소드 서명의 조합입니다. 인터페이스를 사용하여 객체의 동작 집합을 정의합니다.

(메소드와 일반 func의 차이점에 유의하세요)

인터페이스는 일반적인 언어의 인터페이스와는 다른 유형입니다. 메소드를 마무리하는 데만 사용됩니다. 그러나 GO 언어가 함수 기반 객체 지향을 갖도록 만드는 것은 이러한 종류의 융합입니다.

인터페이스의 주요 기능:

1. 객체 지향 설계를 위한 메소드 융합.

2. 다양한 데이터의 전달자로 기능 매개변수 등을 수신하는 데 사용할 수 있습니다.

GO 언어를 옹호하는 인터페이스 지향 프로그래밍도 마찬가지입니다.

2.

2.1 정의

유사한 구조

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

를 사용한 인터페이스 정의일 뿐이며, 데이터 지향 인터페이스에는 사용되지 않습니다.

  • 인터페이스 이름: 유형을 사용하여 인터페이스를 사용자 정의 유형 이름으로 정의합니다. Go 언어 인터페이스의 이름을 지정할 때 일반적으로 단어 뒤에 er를 추가합니다. 예를 들어 쓰기 작업이 있는 인터페이스는 Writer, 문자열 함수가 있는 인터페이스는 Stringer 등으로 불립니다. 인터페이스 이름은 인터페이스의 유형 의미를 강조하는 것이 가장 좋습니다.

  • 메서드 이름: 메소드 이름의 첫 글자를 대문자로 하고 인터페이스 타입 이름의 첫 글자도 대문자로 쓰면 인터페이스가 위치한 패키지 외부의 코드에서 이 메소드에 접근할 수 있습니다.

  • 매개변수 목록, 반환값 목록: 매개변수 목록과 반환값 목록의 매개변수 변수 이름은 생략 가능

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 언어는 어떻게 구현합니까? check 이 유형은 인터페이스입니까?

답변: Go 언어는 매개변수 전달, 매개변수 반환 및 변수 할당 시 유형이 인터페이스를 구현하는지 여부만 확인합니다. 유형 검사 프로세스의 관점에서 보면 컴파일러는 필요할 때만 유형을 검사합니다. 유형이 인터페이스를 구현할 때 인터페이스의 모든 메소드만 구현하면 되며 프로그래밍 언어처럼 명시적으로 선언할 필요가 없습니다. 자바와 같은.

위의 인터페이스를 구현하면 KFC는 구조체 객체를 사용하여 구현되는 반면, Hamberger king은 포인터를 통해 구현되는 것을 알 수 있습니다. 둘의 차이점은 무엇인가요?

답변: 차이점은 인터페이스를 초기화할 때입니다

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

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

그러면 포인터를 사용하여 구조를 구현하고 초기화할 때 왜 작동하지 않습니까?

답변: Go 언어는 매개변수를 값으로 전달합니다.

golang의 인터페이스는 어떤 용도로 사용되나요?

위 그림과 같이 위 코드에서 초기화된 변수 포인터나 구조체와 관계없이 메소드를 호출하면 값 복사가 발생합니다.

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

golang의 인터페이스는 어떤 용도로 사용되나요?

空接口作为函数的参数

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

// 空接口作为函数参数
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 设置到结构体上。

golang의 인터페이스는 어떤 용도로 사용되나요?

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

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

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

golang의 인터페이스는 어떤 용도로 사용되나요?

到这里,我们会发现 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 函数的栈上会包含以下数据:

golang의 인터페이스는 어떤 용도로 사용되나요?

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 与变量的类型比较.

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

위 내용은 golang의 인터페이스는 어떤 용도로 사용되나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.