接口类型

  • 在Go语言中接口(interface)是一种类型,一种抽象的类型。
  • interface是一组method的集合,是duck-type programming的一种体现。接口做的事情就像是定义一个协议(规则)

举例:只要一台机器有洗衣服和甩干的功能,我就称它为洗衣机。不关心属性(数据),只关心行为(方法)

为了保护你的Go语言职业生涯,请牢记接口(interface)是一种类型

为什么要使用接口

下面的代码中定义了猫和狗,然后它们都会叫,你会发现main函数中明显有重复的代码,如果我们后续再加上猪、青蛙等动物的话,我们的代码还会一直重复下去。那我们能不能把它们当成“能叫的动物”来处理呢?

案例:

type Cat struct{}

func (c Cat) Say() string { return "喵喵喵" }

type Dog struct{}

func (d Dog) Say() string { return "汪汪汪" }

func main() {
c := Cat{}
fmt.Println("猫:", c.Say())
d := Dog{}
fmt.Println("狗:", d.Say())
}

Go语言中为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一种抽象的类型。当你看到一个接口类型的值时,你不知道它是什么,唯一知道的是通过它的方法能做什么。

接口的定义

Go语言提倡面向接口编程。

每个接口由数个方法组成,接口的定义格式如下:

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

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

实现接口的条件

一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

针对上面的案例我们实现一个Sayer接口

type Sayer interface {
say()
}

因为Sayer接口里只有一个Say方法,所以我们只需要给Dog和Cat分别实现Say方法就可以实现Sayer接口了。

//定义dog和cat两个结构体:
type dog struct {} type cat struct {} // dog实现了Sayer接口
func (d dog) say() {
fmt.Println("汪汪汪")
} // cat实现了Sayer接口
func (c cat) say() {
fmt.Println("喵喵喵")
} func main() {
// 定义一个Sayer的切片类型
var animal []Sayer // 接口是一个类型
c := cat{}
d := dog{}
animal = append(animal, c, d)
fmt.Println(animal) // [{} {}]
}

接口的实现就是这么简单,只要实现了接口中的所有方法,就实现了这个接口。

接口类型变量

接口类型变量能够存储所有实现了该接口的实例。 例如上面的示例中,Sayer类型的变量能够存储dog和cat类型的变量。

func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := cat{} // 实例化一个cat
b := dog{} // 实例化一个dog
x = a // 可以把cat实例直接赋值给x
x.say() // 喵喵喵
x = b // 可以把dog实例直接赋值给x
x.say() // 汪汪汪
}
// 注意: 接口是一个抽象的类型

值接收者和指针接收者实现接口的区别

使用值接收者实现接口和使用指针接收者实现接口有什么区别呢?接下来我们通过一个例子看一下其中的区别。

我们有一个Mover接口和一个dog结构体。

type Mover interface {
move()
} type dog struct {}

值接收者实现接口

func (d dog) move() {
fmt.Println("狗会动")
}

此时实现接口的是dog类型:

func main() {
var x Mover
var wangcai = dog{} // 旺财是dog类型
x = wangcai // x可以接收dog类型
var fugui = &dog{} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
x.move()
}

从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是dog结构体还是结构体指针*dog类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,dog指针fugui内部会自动求值*fugui

指针接收者实现接口

同样的代码我们再来测试一下使用指针接收者有什么区别:

func (d *dog) move() {
fmt.Println("狗会动")
}
func main() {
var x Mover
var wangcai = dog{} // 旺财是dog类型
x = wangcai // x不可以接收dog类型
var fugui = &dog{} // 富贵是*dog类型
x = fugui // x可以接收*dog类型
}

此时实现Mover接口的是*dog类型,所以不能给x传入dog类型的wangcai,此时x只能存储*dog类型的值。

类型与接口的关系

  • 一个类型实现多个接口

一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。 例如,狗可以叫,也可以动。我们就分别定义Sayer接口和Mover接口,如下:Mover接口。

// 定义两个接口
type Sayer interface {
say()
}
type Mover interface {
move()
} // dog结构体
type dog struct{
name string
} // dog实现say方法
func (d dog) say() {
fmt.Println("汪汪汪")
} // dog实现move方法
func (d dog) move() {
fmt.Println("跑...")
} func main() {
var d = dog{name:"旺财"} // dog实现了say,move方法
var s Sayer
var m Mover
s = d
m = d
s.say() // 汪汪汪
m.move() // 跑...
}
  • 多个类型实现同一接口

Go语言中不同的类型还可以实现同一接口 首先我们定义一个Mover接口,它要求必须有一个move方法。

// Mover 接口
type Mover interface {
move()
} // dog和car结构体
type dog struct {
name string
}
type car struct {
brand string
}
// dog类型实现Mover接口
func (d dog) move() {
fmt.Printf("%s会跑\n", d.name)
} // car类型实现Mover接口
func (c car) move() {
fmt.Printf("%s速度70迈\n", c.brand)
}
func main() {
var x Mover
var a = dog{name: "旺财"}
var b = car{brand: "保时捷"}
x = a
x.move() // 旺财会跑
x = b
x.move() // 保时捷速度70迈
}

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

  • 类型嵌套其他类型实现多个接口

// 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("洗刷刷")
} func main() {
h := haier{dryer{}}
h.wash() // 洗刷刷
h.dry() // 甩一甩
var w WashingMachine
w = h
w.wash() // 洗刷刷
w.dry() // 甩一甩
}

接口嵌套

接口与接口间可以通过嵌套创造出新的接口。

// Sayer 接口
type Sayer interface {
say()
} // Mover 接口
type Mover interface {
move()
} // 接口嵌套
type animal interface {
Sayer // say()
Mover // move()
} type cat struct {
name string
} func (c cat) say() {
fmt.Println("喵喵喵")
} func (c cat) move() {
fmt.Println("猫会动")
} func main() {
var x animal
x = cat{name: "咪咪"}
x.move() // 猫会动
x.say() // 喵喵喵
}

空接口

定义

空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口

空接口类型的变量可以存储任意类型的变量。因为任何类型都实现了空接口。

// 定义一个空接口x
type inter interface {} func main() {
var x inter
s := "Hello 沙河"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}

空接口的应用

  • 空接口作为函数的参数

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

// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}
func main() {
s := "Hello 沙河"
i := 100
b := true
str := struct {
name string
}{name:"xionger"}
show(s) // type:string value:Hello 沙河
show(i) // type:int value:100
show(b) // type:bool value:true
show(str) // type:struct { name string } value:{xionger}
}
  • 空接口作为map的值

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

// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "熊二"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo) // map[age:18 married:false name:熊二]

类型断言

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

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式

x.(T)
  • x:表示类型为interface{}的变量
  • T:表示断言x可能是的类型。

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

func Show(x interface{}){
// 因为这个函数可以接受任意类型的变量,这时候要要用到断言
// 类型断言
v, ok := x.(int)
if !ok {
fmt.Println("断言错误",v)
}else {
fmt.Println("断言正确",v)
}
} func main() {
Show(100) // 断言正确 100
Show("熊二") // 断言错误 0
}

上面的示例中如果要断言多次就需要写多个if判断,这个时候我们可以使用switch语句来实现:

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语言 - 接口的更多相关文章

  1. Swift中对C语言接口缓存的使用以及数组、字符串转为指针类型的方法

    由于Swift编程语言属于上层编程语言,而Swift中由于为了低层的高性能计算接口,所以往往需要C语言中的指针类型,由此,在Swift编程语言刚诞生的时候就有了UnsafePointer与Unsafe ...

  2. C语言接口与实现实例

    一个模块有两部分组成:接口和实现.接口指明模块要做什么,它声明了使用该模块的代码可用的标识符.类型和例程,实现指明模块是如何完成其接口声明的目标的,一个给定的模块通常只有一个接口,但是可能会有许多种实 ...

  3. [转]SQLITE3 C语言接口 API 函数简介

    SQLITE3 C语言接口 API 函数简介 说明:本说明文档属作者从接触 SQLite 开始认识的 API 函数的使用方法, 由本人翻译, 不断更新. /* 2012-05-25 */ int sq ...

  4. 基于Oracle OCI的数据访问C语言接口ORADBI .

    基于Oracle OCI的数据访问C语言接口ORADBI cheungmine@gmail.com Mar. 22, 2008   ORADBI是我在Oracle OCI(Oracle 调用接口)基础 ...

  5. 18 A GIF decoder: an exercise in Go interfaces 一个GIF解码器:go语言接口训练

    A GIF decoder: an exercise in Go interfaces  一个GIF解码器:go语言接口训练 25 May 2011 Introduction At the Googl ...

  6. opencv的C语言接口和C++接口差别(入门篇)

    opencv是一个开源的图像处理库,最经典的1.0版本号提供的接口都是C语言接口. 后来的opencv2.x版本号保留了C语言接口,可是提供了C++接口,当中的C语言接口仅仅是为了向后兼容,而C++接 ...

  7. GO语言学习(十八)Go 语言接口

    Go 语言接口 Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口. 实例 /* 定义接口 */ type interface ...

  8. C语言学习书籍推荐《C语言接口与实现:创建可重用软件的技术》下载

    <C语言接口与实现:创建可重用软件的技术>概念清晰.实例详尽,是一本有关设计.实现和有效使用C语言库函数,掌握创建可重用C语言软件模块技术的参考指南.书中提供了大量实例,重在阐述如何用一种 ...

  9. 07. Go 语言接口

    Go 语言接口 接口本身是调用方和实现方均需要遵守的一种协议,大家按照统一的方法命名参数类型和数量来协调逻辑处理的过程. Go 语言中使用组合实现对象特性的描述.对象的内部使用结构体内嵌组合对象应该具 ...

  10. 浅入浅出 Go 语言接口的原理

    浅入浅出 Go 语言接口的原理 接口是 Go 语言的重要组成部分,它在 Go 语言中通过一组方法指定了一个对象的行为,接口 interface 的引入能够让我们在 Go 语言更好地组织并写出易于测试的 ...

随机推荐

  1. Web Api 实现删除功能接口

    删除数据 [HttpDelete] public ResultModel DeleteDataById(int id) { var result = new ResultModel(); //实例化数 ...

  2. 【C++】虚函数的实现机制

    一.什么是虚函数? 虚函数是在类中由virtual关键字声明的成员函数,并且每一个含有虚函数的类都至少有一个与之对应的虚函数表,其中存放着该类所有虚函数对应的函数指针 在基类中进行如下定义: virt ...

  3. How to let your website login with domain account when using IIS to deploy it?

    如何让你的网站以域账号登录 Select your website in IIS Manager, open Authentication, enable Windows Authentication ...

  4. C++ 01 基础知识点

    1.为某一特定的问题而设计的指令序列称为:程序 2.‘32位微型计算机’中的32位指的是:机器的字长 3.存储设备中,存取速度最快的是:Cache 4.指令的操作码表示的是:作何操作 5.一条指令由哪 ...

  5. C语言是什么

    大家对于Java可能并不陌生,那你对c语言了解多少呢,今天小编带大家来了解c语言是什么. c语言是一门面向过程.抽象化的通用程序设计语言,广泛应用于底层开发.C语言具有高效.灵活.功能丰富.表达力强和 ...

  6. Docker 下的Zookeeper以及.ne core 的分布式锁

    单节点 1.拉取镜像:docker pull zookeeper 2.运行容器 a.我的容器同一放在/root/docker下面,然后创建相应的目录和文件, mkdir zookeeper cd zo ...

  7. django开发_七牛云CNAME解析

    CNAME 简介 CNAME 即指别名记录,也被称为规范名字.这种记录允你将多个名字映射到同一台计算机. 当需要将域名指向另一个域名,再由另一个域名提供 ip地址,就需要添加 CNAME 记录. 为什 ...

  8. 记:使用IScroll.js 开发picker日历组件遇到的问题及经验总结

    IScroll中文文档 第一个问题: 边界留白 就是这种,上边界(最小),下边界(最大)有两个列表的位置是不能选择的.解决的办法是: 在HTML中,添加空白节点就行了. 第二个问题:初始化之后的滚动停 ...

  9. mysql 中的 not like 另一种简化方法。

    第一种 not like 方法 select * from table where `zongbu` not like '%北京%' and `zongbu` not like '%上海%' and ...

  10. 基于OpenGL三维软件开发

    实验原理: OpenGL在MFC下编程原理---- Windows操作系统对OpenGL的支持 在Windows下用GDI作图必须通过设备上下文(DeviceContext简写DC)调用相应的函数:用 ...