Go语言基础之接口

接口类型

一个接口类型就是一组方法的集合,它规定了需要实现的所有方法。

接口的定义

每个接口类型由任意个方法签名组成,接口的定义格式如下:

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

其中:

  • 接口类型名:Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有关闭操作的接口叫closer等。接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。

举个例子,定义一个包含Write方法的Writer接口。

type Writer interface{Write([]byte) error
}

当你看到一个Writer接口类型的值时,你不知道它是什么,唯一知道的就是可以通过调用它的Write方法来做一些事情。

实现接口的条件

接口就是规定了一个需要实现的方法列表,在 Go 语言中一个类型只要实现了接口中规定的所有方法,那么我们就称它实现了这个接口。

我们定义的Singer接口类型,它包含一个Sing方法。

// Singer 接口
type Singer interface {Sing()
}

我们有一个Bird 结构体类型如下。

type Bird struct {}

因为Singer接口只包含一个Sing方法,所以只需要给Bird结构体添加一个Sing方法就可以满足Singer接口的要求。

// Sing Bird类型的Sing方法
func (b Bird) Sing() {fmt.Println("汪汪汪")
}

这样就称为Bird实现了Singer接口。

通过接口实现多态

现在假设我们的代码世界里有很多小动物,下面的代码片段定义了猫和狗,它们饿了都会叫。

type Sayer interface {Say()
}

定义多个小动物结构体:

type Cat struct{}func (c Cat) Say() {fmt.Println("喵喵喵~")
}type Dog struct{}func (d Dog) Say() {fmt.Println("汪汪汪~")
}

 这个时候调用Say可以通过多态方式

// MakeHungry 饿肚子了...
func MakeHungry(s Sayer) {s.Say()
}

接口类型变量

那实现了接口又有什么用呢?一个接口类型的变量能够存储所有实现了该接口的类型变量。

例如在上面的示例中,DogCat类型均实现了Sayer接口,此时一个Sayer类型的变量就能够接收CatDog类型的变量。

var x Sayer // 声明一个Sayer类型的变量x
a := Cat{}  // 声明一个Cat类型变量a
b := Dog{}  // 声明一个Dog类型变量b
x = a       // 可以把Cat类型变量直接赋值给x
x.Say()     // 喵喵喵
x = b       // 可以把Dog类型变量直接赋值给x
x.Say()     // 汪汪汪

值接收者和指针接收者

定义一个Mover接口,包含一个Move方法。

// Mover 定义一个接口类型
type Mover interface {Move()
}

值接收者实现接口

我们定义一个Dog结构体类型,并使用值接收者为其定义一个Move方法。

// Dog 狗结构体类型
type Dog struct{}// Move 使用值接收者定义Move方法实现Mover接口
func (d Dog) Move() {fmt.Println("狗会动")
}

此时实现Mover接口的是Dog类型。

var x Mover    // 声明一个Mover类型的变量xvar d1 = Dog{} // d1是Dog类型
x = d1         // 可以将d1赋值给变量x
x.Move()var d2 = &Dog{} // d2是Dog指针类型
x = d2          // 也可以将d2赋值给变量x
x.Move()

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是结构体类型还是对应的结构体指针类型的变量都可以赋值给该接口变量。

指针接收者实现接口

测试一下使用指针接收者实现接口有什么区别。

// Cat 猫结构体类型
type Cat struct{}// Move 使用指针接收者定义Move方法实现Mover接口
func (c *Cat) Move() {fmt.Println("猫会动")
}

此时实现Mover接口的是*Cat类型,我们可以将*Cat类型的变量直接赋值给Mover接口类型的变量x

var c1 = &Cat{} // c1是*Cat类型
x = c1          // 可以将c1当成Mover类型
x.Move()

但是不能给将Cat类型的变量赋值给Mover接口类型的变量x

// 下面的代码无法通过编译
var c2 = Cat{} // c2是Cat类型
x = c2         // 不能将c2当成Mover类型

由于Go语言中有对指针求值的语法糖,对于值接收者实现的接口,无论使用值类型还是指针类型都没有问题。但是我们并不总是能对一个值求址,所以对于指针接收者实现的接口要额外注意。

总结:其实这里还是针对的是go语言的指针变量和普通变量的区别。

类型与接口的关系

一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

// Sayer 接口
type Sayer interface {Say()
}// Mover 接口
type Mover interface {Move()
}

Dog既可以实现Sayer接口,也可以实现Mover接口。

type Dog struct {Name string
}// 实现Sayer接口
func (d Dog) Say() {fmt.Printf("%s会叫汪汪汪\n", d.Name)
}// 实现Mover接口
func (d Dog) Move() {fmt.Printf("%s会动\n", d.Name)
}

同一个类型实现不同的接口互相不影响使用。

var d = Dog{Name: "旺财"}var s Sayer = d
var m Mover = ds.Say()  // 对Sayer类型调用Say方法
m.Move() // 对Mover类型调用Move方法

多种类型实现同一接口

Go语言中不同的类型还可以实现同一接口。例如在我们的代码世界中不仅狗可以动,汽车也可以动。我们可以使用如下代码体现这个关系。

// 实现Mover接口
func (d Dog) Move() {fmt.Printf("%s会动\n", d.Name)
}// Car 汽车结构体类型
type Car struct {Brand string
}// Move Car类型实现Mover接口
func (c Car) Move() {fmt.Printf("%s速度70迈\n", c.Brand)
}

这样我们在代码中就可以把狗和汽车当成一个会动的类型来处理,不必关注它们具体是什么,只需要调用它们的Move方法就可以了。

var obj Moverobj = Dog{Name: "旺财"}
obj.Move()obj = Car{Brand: "宝马"}
obj.Move()

上面的代码执行结果如下:

旺财会跑
宝马速度70迈

一个接口的所有方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。

// WashingMachine 洗衣机
type WashingMachine interface {wash()dry()
}// 甩干器
type dryer struct{}// 实现WashingMachine接口的dry()方法
func (d dryer) dry() {fmt.Println("甩一甩")
}// 海尔洗衣机
type haier struct {dryer //嵌入甩干器
}// 实现WashingMachine接口的wash()方法
func (h haier) wash() {fmt.Println("洗刷刷")
}

接口组合

接口与接口之间可以通过互相嵌套形成新的接口类型,例如Go标准库io源码中就有很多接口之间互相组合的示例。

// src/io/io.gotype Reader interface {Read(p []byte) (n int, err error)
}type Writer interface {Write(p []byte) (n int, err error)
}type Closer interface {Close() error
}// ReadWriter 是组合Reader接口和Writer接口形成的新接口类型
type ReadWriter interface {ReaderWriter
}// ReadCloser 是组合Reader接口和Closer接口形成的新接口类型
type ReadCloser interface {ReaderCloser
}// WriteCloser 是组合Writer接口和Closer接口形成的新接口类型
type WriteCloser interface {WriterCloser
}

对于这种由多个接口类型组合形成的新接口类型,同样只需要实现新接口类型中规定的所有方法就算实现了该接口类型。

接口也可以作为结构体的一个字段,我们来看一段Go标准库sort源码中的示例。

// src/sort/sort.go// Interface 定义通过索引对元素排序的接口类型
type Interface interface {Len() intLess(i, j int) boolSwap(i, j int)
}// reverse 结构体中嵌入了Interface接口
type reverse struct {Interface
}

通过在结构体中嵌入一个接口类型,从而让该结构体类型实现了该接口类型,并且还可以改写该接口的方法。

// Less 为reverse类型添加Less方法,重写原Interface接口类型的Less方法
func (r reverse) Less(i, j int) bool {return r.Interface.Less(j, i)
}

Interface类型原本的Less方法签名为 Less(i, j int) bool,此处重写为r.Interface.Less(j, i),即通过将索引参数交换位置实现反转。

在这个示例中还有一个需要注意的地方是reverse结构体本身是不可导出的(结构体类型名称首字母小写),sort.go中通过定义一个可导出的Reverse函数来让使用者创建reverse结构体实例。

func Reverse(data Interface) Interface {return &reverse{data}
}

这样做的目的是保证得到的reverse结构体中的Interface属性一定不为nil,否者r.Interface.Less(j, i)就会出现空指针panic。

此外在Go内置标准库database/sql中也有很多类似的结构体内嵌接口类型的使用示例,各位读者可自行查阅。

空接口

空接口的定义

空接口是指没有定义任何方法的接口类型。因此任何类型都可以视为实现了空接口。也正是因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值。

package mainimport "fmt"// 空接口// Any 不包含任何方法的空接口类型
type Any interface{}// Dog 狗结构体
type Dog struct{}func main() {var x Anyx = "你好" // 字符串型fmt.Printf("type:%T value:%v\n", x, x)x = 100 // int型fmt.Printf("type:%T value:%v\n", x, x)x = true // 布尔型fmt.Printf("type:%T value:%v\n", x, x)x = Dog{} // 结构体类型fmt.Printf("type:%T value:%v\n", x, x)
}

通常我们在使用空接口类型时不必使用type关键字声明,可以像下面的代码一样直接使用interface{}

var x interface{}  // 声明一个空接口类型变量x

空接口的应用

空接口作为函数的参数

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

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

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

// 空接口作为map值var studentInfo = make(map[string]interface{})studentInfo["name"] = "沙河娜扎"studentInfo["age"] = 18studentInfo["married"] = falsefmt.Println(studentInfo)

接口值

由于接口类型的值可以是任意一个实现了该接口的类型值,所以接口值除了需要记录具体之外,还需要记录这个值属于的类型。也就是说接口值由“类型”和“值”组成,鉴于这两部分会根据存入值的不同而发生变化,我们称之为接口的动态类型动态值

接口值示例

我们接下来通过一个示例来加深对接口值的理解。

下面的示例代码中,定义了一个Mover接口类型和两个实现了该接口的DogCar结构体类型。

type Mover interface {Move()
}type Dog struct {Name string
}func (d *Dog) Move() {fmt.Println("狗在跑~")
}type Car struct {Brand string
}func (c *Car) Move() {fmt.Println("汽车在跑~")
}

首先,我们创建一个Mover接口类型的变量m

var m Mover

此时,接口变量m是接口类型的零值,也就是它的类型和值部分都是nil,就如下图所示。

接口值示例

我们可以使用m == nil来判断此时的接口值是否为空。

fmt.Println(m == nil)  // true

**注意:**我们不能对一个空接口值调用任何方法,否则会产生panic。

m.Move() // panic: runtime error: invalid memory address or nil pointer dereference

接下来,我们将一个*Dog结构体指针赋值给变量m

m = &Dog{Name: "旺财"}

此时,接口值m的动态类型会被设置为*Dog,动态值为结构体变量的拷贝。

接口值示例

然后,我们给接口变量m赋值为一个*Car类型的值。

var c *Car
m = c

这一次,接口值m的动态类型为*Car,动态值为nil

接口值示例

**注意:**此时接口变量mnil并不相等,因为它只是动态值的部分为nil,而动态类型部分保存着对应值的类型。

fmt.Println(m == nil) // false

接口值是支持相互比较的,当且仅当接口值的动态类型和动态值都相等时才相等。

var (x Mover = new(Dog)y Mover = new(Car)
)
fmt.Println(x == y) // false

但是有一种特殊情况需要特别注意,如果接口值保存的动态类型相同,但是这个动态类型不支持互相比较(比如切片),那么对它们相互比较时就会引发panic。

var z interface{} = []int{1, 2, 3}
fmt.Println(z == z) // panic: runtime error: comparing uncomparable type []int

类型断言

接口值可能赋值为任意类型的值,那我们如何从接口值获取其存储的具体数据呢?

我们可以借助标准库fmt包的格式化打印获取到接口值的动态类型。

var m Moverm = &Dog{Name: "旺财"}
fmt.Printf("%T\n", m) // *main.Dogm = new(Car)
fmt.Printf("%T\n", m) // *main.Car

fmt包内部其实是使用反射的机制在程序运行时获取到动态类型的名称。关于反射的内容我们会在后续章节详细介绍。

而想要从接口值中获取到对应的实际值需要使用类型断言,其语法格式如下。

x.(T)

其中:

  • x:表示接口类型的变量
  • T:表示断言x可能是的类型。

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。

举个例子:

var n Mover = &Dog{Name: "旺财"}
v, ok := n.(*Dog)
if ok {fmt.Println("类型断言成功")v.Name = "富贵" // 变量v是*Dog类型
} else {fmt.Println("类型断言失败")
}

如果对一个接口值有多个实际类型需要判断,推荐使用switch语句来实现。

// justifyType 对传入的空接口类型变量x进行类型断言
func justifyType(x interface{}) {switch v := x.(type) {case string:fmt.Printf("x is a string,value is %v\n", v)case int:fmt.Printf("x is a int is %v\n", v)case bool:fmt.Printf("x is a bool is %v\n", v)default:fmt.Println("unsupport type!")}
}

由于接口类型变量能够动态存储不同类型值的特点,所以很多初学者会滥用接口类型(特别是空接口)来实现编码过程中的便捷。只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。切记不要为了使用接口类型而增加不必要的抽象,导致不必要的运行时损耗。

在 Go 语言中接口是一个非常重要的概念和特性,使用接口类型能够实现代码的抽象和解耦,也可以隐藏某个功能的内部实现,但是缺点就是在查看源码的时候,不太方便查找到具体实现接口的类型。

相信很多读者在刚接触到接口类型时都会有很多疑惑,请牢记接口是一种类型,一种抽象的类型。区别于我们在之前章节提到的那些具体类型(整型、数组、结构体类型等),它是一个只要求实现特定方法的抽象类型。

小技巧: 下面的代码可以在程序编译阶段验证某一结构体是否满足特定的接口类型。

// 摘自gin框架routergroup.go
type IRouter interface{ ... }type RouterGroup struct { ... }var _ IRouter = &RouterGroup{}  // 确保RouterGroup实现了接口IRouter

上面的代码中也可以使用var _ IRouter = (*RouterGroup)(nil)进行验证。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/653222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源】基于JAVA+Vue+SpringBoot的智慧家政系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询家政服务4.2 新增单条服务订单4.3 新增留言反馈4.4 小程序登录4.5 小程序数据展示 五、免责说明 一、摘要 1.1 项目介绍 基于微信小程序JAVAVueSpringBootMySQL的智慧家政系统&#xff0…

Qt项目文件以及对象树

"在哪里走散,你都会找到我~" 前篇,我们仅仅对Qt创建了第一个简单的项目。相比于使用其他IDE创建工程项目,Qt会为自动创建诸如:.pro、.h\.cpp、.iu等文件,这些文件到底是什么?我们在使用Qt时 应该…

聊聊Git合并和变基

一、 Git Merge 合并策略 1.1 Fast-Forward Merge&#xff08;快进式合并&#xff09; //在分支1下操作&#xff0c;会将分支1合并到分支2中 git merge <分支2>最简单的合并算法&#xff0c;它是在一条不分叉的两个分支之间进行合并。快进式合并是默认的合并行为&#…

mysql注入联合查询

环境搭建 下载复现漏洞的包 下载小皮面板 将下载好的文件解压在小皮面板的phpstudy_pro\WWW路径下 将这个文件phpstudy_pro\WWW\sqli-labs-php7-master\sql-connections\db-creds.inc 中的密码更改为小皮面板中的密码 选择php版本 在小皮中启动nginx和数据库 使用环回地址访…

JavaScript 学习笔记(JS进阶 Day4)

「写在前面」 本文为 b 站黑马程序员 pink 老师 JavaScript 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。推荐先按顺序阅读往期内容&#xff1a; 1. JavaScript 学习笔记&#xff08;Day1&#xff09; 2. JavaSc…

NodeJs环境安装与配置

最近电脑重装了系统&#xff0c;开发环境啥的都得重装&#xff0c;顺便记录下 nodeJs 的安装与配置&#xff0c;方便需要的同学查看&#xff0c;也方便自己以后查找。 安装 下载地址&#xff1a;https://nodejs.cn/download/ 根据需要选择自己环境需要的下载即可&#xff0c;…

【cdh】hive执行SQL提示缺少3.0.0-cdh6.3.2-mr-framework.tar.gz文件

问题&#xff1a;执行SQL报错提示缺少文件 异常信息如下 在hdfs上查看的时候连文件夹都没有&#xff0c;所以这个异常会抛出&#xff0c;但是我是基于CDH搭建的&#xff0c;可以直接基于下面操作 执行完成之后查看HDFS文件 重新执行SQL发现可以正常执行了

web前端项目-实现录音功能【附源码】

录音功能 运行效果&#xff1a;本项目可实现录音软件的录音、存储、播放等功能 HTML源码&#xff1a; &#xff08;1&#xff09;index.html&#xff1a; <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/h…

【BUG】联想Y7000电池电量为0且无法充电解决方案汇总

因为最近火灾很多&#xff0c;所以昨天夜晚睡觉的时候把插线板电源关掉了&#xff0c;电脑也关机了。 各位一定要注意用电安全&#xff0c;网上的那些事情看着真的很难受qvq。 第二天早上起床的时候一看发现电脑直接没电了&#xff0c;插上电源后也是显示 你一定要冲进去啊(ू˃…

springboot外出务工人员信息管理系统源码和论文

网络的广泛应用给生活带来了十分的便利。所以把疫情防控期间某村外出务工人员信息管理与现在网络相结合&#xff0c;利用java技术建设疫情防控期间某村外出务工人员信息管理系统&#xff0c;实现疫情防控期间某村外出务工人员信息的信息化。则对于进一步提高疫情防控期间某村外…

2024阿里云和腾讯云的第一战打响:搭建《幻兽帕鲁》私服游戏

为了搭建《幻兽帕鲁》游戏私服&#xff0c; 2024年阿里云 VS 腾讯云的第一场战争开始了…… 事情是这样的&#xff1a; 1月19日&#xff0c;最离谱新游 《幻兽帕鲁》突然爆火了&#xff0c;这是一款日本开发商展耗费4年开发的冒险类游戏&#xff0c;这款戏一推出就迅速俘获了…

DOM 型 XSS 攻击演示(附链接)

一、介绍 DOM&#xff08;Document Object Model&#xff09;型 XSS&#xff08;Cross-Site Scripting&#xff09;攻击是一种 Web 应用程序中的安全漏洞&#xff0c;其特点是攻击者成功地注入了恶意脚本&#xff0c;这些脚本在用户的浏览器中执行&#xff0c;从而导致恶意行为…

AcWing 895. 最长上升子序列(DP序列模型)

[题目概述] 给定一个长度为 N 的数列&#xff0c;求数值严格单调递增的子序列的长度最长是多少。 输入格式 第一行包含整数 N。 第二行包含 N 个整数&#xff0c;表示完整序列。 输出格式 输出一个整数&#xff0c;表示最大长度。 数据范围 1 ≤ N ≤ 1000 &#xff0c; …

【刷题】 leetcode 面试题 08.05.递归乘法

递归乘法 1 题目描述2 思路一&#xff08;返璞归真版&#xff09;3 思路二&#xff08;二进制乘法器版&#xff09;4 思路三&#xff08;变态版&#xff09;Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读下一篇文章见&#xff01;&#xff01;&#xff01; 1 题目…

不确定优化入门:用简单实例讲明白随机规划、鲁棒优化和分布鲁棒优化

文章目录 1 引言2 学习动机3 经典问题4 解决方案4.1 忽略不确定性4.2 随机规划4.3 鲁棒优化4.4 分布鲁棒优化 5 总结相关阅读 1 引言 按2024的原定计划&#xff0c;今年开始要学习不确定优化了。 粗略翻阅了一些相关的书籍和教程&#xff0c;大都包含许多数学公式&#xff0c…

SpringBoot引入主盘探活定时任务

主盘探活通常是指检查存储设备&#xff08;例如硬盘&#xff09;是否可读写&#xff0c;但在Java中并没有直接针对硬件级别的磁盘探活API。然而&#xff0c;我们可以模拟一个场景&#xff0c;即检查某个目录或文件是否可以被Java程序正常读写&#xff0c;以此作为主盘活跃的一个…

HCIP复习课(bgp实验)

1、ip配置&#xff1a; R1&#xff1a; R2&#xff1a; R9&#xff1a; R10&#xff1a; R11&#xff1a; R3&#xff1a; R4&#xff1a; R5&#xff1a; R6&#xff1a; R7&#xff1a; R8&#xff1a; 2、隧道配置&#xff1a; R2&#xff1a; 静态&#xff1a; R10&am…

第15次修改了可删除可持久保存的前端html备忘录:换了一个容器时钟,匹配背景主题:现代深色

第15次修改了可删除可持久保存的前端html备忘录&#xff1a;换了一个容器时钟&#xff0c;匹配背景主题&#xff1a;现代深色 备忘录代码 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv&qu…

WSL2 Debian系统添加支持SocketCAN

本人最近在使用WSL2&#xff0c;Linux系统选择的是Debian&#xff0c;用起来很不错&#xff0c;感觉可以代替VMware Player虚拟机。 但是WSL2 Debian默认不支持SocketCAN&#xff0c;这就有点坑了&#xff0c;由于本人经常要使用SocketCAN功能&#xff0c;所以决定让Debian支持…

Axolotl:一款极简的大模型微调(Finetune)开源框架

今天给大家分享一款工具&#xff0c;Axolotl[1] 是一个旨在简化各种AI模型的微调过程的工具&#xff0c;支持多种配置和架构。 特点&#xff1a; 可训练各种 Huggingface 模型&#xff0c;如 llama、pythia、falcon、mpt支持 fullfinetune、lora、qlora、relora 和 gptq使用简…