搜索
首页后端开发Golanggolang的接口有啥用

golang的接口有啥用

Jan 04, 2023 pm 08:42 PM
golang接口go语言

在golang中,接口是一种类型,是用来将对方法进行一个收束,其作用是:1、作为方法的收束器,进行面向对象设计;2、作为各种数据的承载者,可以用来接收函数参数等。接口的定义语法“type 接口类型名 interface{方法名( 参数列表1 ) 返回值列表}”;当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。

golang的接口有啥用

本教程操作环境: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 语言在传递参数时都是传值的。

1.png

如上图所示,无论上述代码中初始化的变量指针还是结构体,使用 调用方法时都会发生值拷贝:

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

2.png

空接口作为函数的参数

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

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

3.png

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

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

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

4.png

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

5.png

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
Golang vs. Python:利弊Golang vs. Python:利弊Apr 21, 2025 am 12:17 AM

Golangisidealforbuildingscalablesystemsduetoitsefficiencyandconcurrency,whilePythonexcelsinquickscriptinganddataanalysisduetoitssimplicityandvastecosystem.Golang'sdesignencouragesclean,readablecodeanditsgoroutinesenableefficientconcurrentoperations,t

Golang和C:并发与原始速度Golang和C:并发与原始速度Apr 21, 2025 am 12:16 AM

Golang在并发性上优于C ,而C 在原始速度上优于Golang。1)Golang通过goroutine和channel实现高效并发,适合处理大量并发任务。2)C 通过编译器优化和标准库,提供接近硬件的高性能,适合需要极致优化的应用。

为什么要使用Golang?解释的好处和优势为什么要使用Golang?解释的好处和优势Apr 21, 2025 am 12:15 AM

选择Golang的原因包括:1)高并发性能,2)静态类型系统,3)垃圾回收机制,4)丰富的标准库和生态系统,这些特性使其成为开发高效、可靠软件的理想选择。

Golang vs.C:性能和速度比较Golang vs.C:性能和速度比较Apr 21, 2025 am 12:13 AM

Golang适合快速开发和并发场景,C 适用于需要极致性能和低级控制的场景。1)Golang通过垃圾回收和并发机制提升性能,适合高并发Web服务开发。2)C 通过手动内存管理和编译器优化达到极致性能,适用于嵌入式系统开发。

golang比C快吗?探索极限golang比C快吗?探索极限Apr 20, 2025 am 12:19 AM

Golang在编译时间和并发处理上表现更好,而C 在运行速度和内存管理上更具优势。1.Golang编译速度快,适合快速开发。2.C 运行速度快,适合性能关键应用。3.Golang并发处理简单高效,适用于并发编程。4.C 手动内存管理提供更高性能,但增加开发复杂度。

Golang:从Web服务到系统编程Golang:从Web服务到系统编程Apr 20, 2025 am 12:18 AM

Golang在Web服务和系统编程中的应用主要体现在其简洁、高效和并发性上。1)在Web服务中,Golang通过强大的HTTP库和并发处理能力,支持创建高性能的Web应用和API。2)在系统编程中,Golang利用接近硬件的特性和对C语言的兼容性,适用于操作系统开发和嵌入式系统。

Golang vs.C:基准和现实世界的表演Golang vs.C:基准和现实世界的表演Apr 20, 2025 am 12:18 AM

Golang和C 在性能对比中各有优劣:1.Golang适合高并发和快速开发,但垃圾回收可能影响性能;2.C 提供更高性能和硬件控制,但开发复杂度高。选择时需综合考虑项目需求和团队技能。

Golang vs. Python:比较分析Golang vs. Python:比较分析Apr 20, 2025 am 12:17 AM

Golang适合高性能和并发编程场景,Python适合快速开发和数据处理。 1.Golang强调简洁和高效,适用于后端服务和微服务。 2.Python以简洁语法和丰富库着称,适用于数据科学和机器学习。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

螳螂BT

螳螂BT

Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)