go语言—接口

  • go语言中接口是一种类型,一种抽象的类型
  • 接口是一种由程序员定义的类型,一个接口类型就是一组方法的集合,规定了需要实现的所有方法
  • 接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计

1、接口的定义

接口就是一系列方法的集合,但是还没有实现,需要对象自己去实现

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

1、接口类型名:go语言在接口命名时,一般会在单词后面添加er,例如有写操作的接口叫Writer,关闭操作的接口叫Closer
2、方法名:当方法名首字母是大写且这个接口类型名首字母也大写时,这个方法可以被接口所在的包之外的代码访问
3、参数列表、返回值列表:参数变量名可以省略

// 定义一个接口,包含Write方法
type Writer interface{
	Write([]byte) error //这个接口可以调用 Write()方法写入一个字节数组([]byte),返回值可能发生的错误(err error)
}

2、实现接口的条件

条件一:go语言中一个类型只要实现了接口中的所有方法,那么就称它实现了接口
条件二:接口方法与实现接口的类型方法格式一致,如果实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现

//定义一个Duck接口类型,包含speak和run两个方法
type Duck interface{
	Speak(s string)
	run()
}
//定义结构体来实现接口
type oneDuck struct{
	name string
	age int
}
type twoDuck struct{
	name string
	age int
}
//oneDuck实现speak和run方法
func(one oneDuck) Speak(s string){
	fmt.Printf("鸭子会说话:我叫%s,我%d岁,我是%s\n",one.name,one.age,s)
}
func(one oneDuck) run(){
	fmt.Println(one.name,"走路歪歪扭扭")
}
//twoDuck实现speak和run方法
func(two twoDuck) Speak(s string){
	fmt.Printf("鸭子会说话:我叫%s,我%d岁,我是%s\n",two.name,two.age,s)
}
func(two twoDuck) run(){
	fmt.Printf("%s走路好看", two.name)
}

func main(){ 
	//实例化oneDuck
	ODuck := oneDuck{"盐水鸭"12}
	//实例化twoDuck
	TDuck := twoDuck{"烤鸭",34}
	//调用方法
	ODuck.Speak("呆逼")
	ODuck.run()
	TDuck.Speak("傻逼")
	TDuck.run()
}

哪怕只有一个方法没有实现,都不算实现接口,当接口所有方法被实现时会出现以下图标
在这里插入图片描述

2、接口类型变量

一个接口类型变量能存储所有实现了该接口的自定义类型变量

例如Dog和Cat类型都实现了Sayer接口,此时Sayer接口类型的变量就能够接收Dog和Cat类型的变量

var x Sayer //声明Sayer接口类型的变量x
a := Cat{} //声明Cat类型变量a
b := Dog{} //声明Dog类型变量b
x = a //可以把Cat类型的变量a赋值给x
x.Say() //变量x可以调用结构体方法
x = b

3、值接收器和指针接收器

对于实现接口来说使用值接收器和指针接收器有什么区别呢

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

3.1、值接收器实现接口

定义一个Dog结构体类型,使用值接收器为其定义一个Move方法
// Dog结构体类型
type Dog struct{}
//使用值接收定义Move方法实现Mover接口
func(d Dog) Move(){
	fmt.Println("狗在叫")
}

此时实现Mover接口的是Dog类型
var x Mover //声明一个Mover类型的变量x
var d1 = Dog{} //声明一个Dog类型的变量d1
x = d1 //将d1赋值给x
x.Move()

var d2 = &Dog{} //d2是Dog的指针类型
x = d2 //也可以将d2赋值给x,因为x就是指针类型
x.Move()

上述代码中,使用值接收器实现接口后,结构体类型和结构体指针类型的变量都可以赋值给接口变量

3.2、指针接收器实现接口

//Cat结构体类型
type Cat struct{}
//使用指针接收器定义方法Move
func(c *Cat) Move(){
	fmt.Println("猫在吃饭")
}

此时实现Mover接口的是*Cat类型,可以讲*Cat类型的变量直接赋值给Mover类型的变量x
var c1 = &Cat{} //c1是Cat指针类型
x = c1 //可以将c1当作Mover类型

但不能将Cat类型的变量赋值给Mover类型变量x
//以下无法编译通过
var c2 = Cat{} //c2是Cat类型
x = c2 //不能将c2当作Mover类型

4、类型与接口的关系

4.1、一个类型实现多个接口
一个自定义类型可以实现多个接口,接口间彼此独立不知道到对方的实现

// 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 //d赋值给Sayer类型变量
var m Mover = d

s.Say()  // 对Sayer类型调用Say方法
m.Move() // 对Mover类型调用Move方法

4.2、多种类型实现同一接口

// 实现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 Mover
obj = Dog{Name: "旺财"}
obj.Move()
obj = Car{Brand: "宝马"}
obj.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("洗刷刷")
}

5、接口嵌套组合

  • 接口与接口之间可以通过互相嵌套形成新的接口类型
  • 一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用
//Go语言的 io 包中定义了写入器(Writer)、关闭器(Closer)和写入关闭器(WriteCloser)3 个接口,代码如下:
type Writer interface {
    Write(p []byte) (n int, err error)
}
type Closer interface {
    Close() error
}
//接口嵌套
type WriteCloser interface { 
    Writer
    Closer
}

//在代码中使用接口进行嵌套组合
type device struct {}
func (d *device) Write(p []byte) (int, error) {
	return 0, nil
}
func (d *device) Close() error {
	return nil
}

6、空接口

6.1、空接口的定义

  • 空接口是指没有定义任何方法的接口类型
  • 任何类型都可以视为实现了空接口
  • 因为空接口类型的这个特性,空接口类型的变量可以存储任意类型的值
// Any 不包含任何方法的空接口类型
type Any interface{}

// Dog 狗结构体
type Dog struct{}

func main() {
	var x Any

	x = "你好" // 字符串型
	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关键字声明:var x interface{} // 声明一个空接口类型变量x

6.2、空接口的应用

1、空接口作为函数的参数
使用空接口可以接收任意类型的函数参数
// 空接口作为函数参数
func show(a interface{}) {
	fmt.Printf("type:%T value:%v\n", a, a)
}

2、空接口作为map的值
使用空接口实现可以保存任意值的字典
// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "沙河娜扎"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)

7、接口值

  • 接口类型的值可以是任意一个实现了该接口的类型值,所以接口值除了需要记录具体值之外,还需要记录这个值属于的类型
  • 接口值由“类型”和“值”组成
    在这里插入图片描述
//定义了一个Mover接口类型和两个实现了该接口的Dog和Car结构体类型
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
在这里插入图片描述
此时接口变量m与nil并不相等,因为它只是动态值的部分为nil,而动态类型部分保存着对应值的类型

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

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

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

8、类型断言

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

//可以借助标准库fmt包的格式化打印获取到接口值的动态类型
var m Mover

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

m = new(Car)
fmt.Printf("%T\n", m) // *main.Car

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

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!")
	}
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值