Go——接口基本概念

1、接口声明

Go语言的接口分为接口字面量类型和接口命名类型,接口的声明使用interface关键字。
接口字面量类型的声明语法如下:

interface{
	Methodsignature1
	MethodSignature2
}

接口命名类型使用ype关键字声明,语法如下:

type InterfaceName interface {
	Methodsignature1
	Methodsignature2
}

使用接口字面量的场景很少,一般只有空接口interface类型变量的声明才会使用。

接口定义大括号内可以是方法声明的集合,也可以嵌入另一个接口类型匿名字段,还可以是二者的混合。接口支持嵌入匿名接口字段,就是一个接口定义里面可以包括其他接口,Go编译器会自动进行展开处理,有点类似C语言中宏的概念。例如:

package main

func main() {
	
}

type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

//如下3种声明是等价的,最终的展开形式都是第3种
type ReadWriter interface {
	Reader
	Writer
}

type ReadWriter interface {
	Reader
	Write(p []byte) (n int, err error)
}

type ReadWriter interface {
	Read(p []byte) (n int, err error)
	Write(p []byte) (n int, err error)
}
方法声明

由函数和类型系统章节可知:Go中的函数没有使用“函数声明”,类型的方法本质上就是函数的一种特殊形式。我们提到了“方法声明”的概念,而不是使用“方法签名”。这里有必要澄清一下这两个概念:严格意义上的函数签名是函数的字面量类型,函数签名是不包括函数名的,而函数声明是指带上函数名的函数签名。同理,对于方法也是一样,接口定义使用方法声明,而不是方法签名,因为方法名是接口的组成部分

//方法声明=方法名+方法签名
MethodName (InputTypeList) OutputTypeList

接口中的“方法声明”非常类似于C语言中的函数声明的概念,Go编译器在做接口匹配判断时是严格校验方法名称和方法签名的。

声明新接口类型的特点
  1. 接口的命名一般以"er"结尾
  2. 接口定义的内部方法声明不需要func引导
  3. 在接口定义中,只有方法声明没有方法实现。

2、接口初始化

单纯的声明一个接口变量没有任何意义,接口只有被初始化为具体的类型时才有意义。接口作为一个胶水层或抽象层,起到抽象和适配的作用。没有初始化的接口变量,其默认值是nil。

var i io.Reader
fmt.Printf("%T\n", i)//nil

接口绑定具体类型的实例的过程称为接口初始化。接口变量支持两种直接初始化方法,具体如下。

实例赋值接口

如果具体类型实例的方法集是某个接口的方法集的超集,则称该具体类型实现了接口,可以将该具体类型的实例直接赋值给接口类型的变量,此时编译器会进行静态的类型检查。接口被初始化后,调用接口的方法就相当于调用接口绑定的具体类型的方法,这就是接口调用的语义。

接口变量赋值接口变量

已经初始化的接口类型变量a直接赋值给另一种接口变量b,要求b的方法集是a的方法集的子集。此时Go编译器会在编译时进行方法集静态检查。这个过程也是接口初始化的一种方式,此时接口变量b绑定的具体实例是接口变量a绑定的具体实例的副本。例如:

file := os.OpenFile("notes.txt",os.O_RDWRIOs.O_CREATE,0755)
var rw io.ReadWriter = file
//io.ReadWriter接口可以直接赋值给io.Writer接口变量
var w io.Writer = rw

接口方法调用

接口方法调用和普通的函数调用是有区别的。接口方法调用的最终地址是在运行期决定的,将具体类型变量赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,实际上是间接地调用实例的方法。接口方法调用不是一种直接的调用,有一定的运
行时开销(直接调用未初始化的接口变量的方法会产生panic。例如:

package main

func main() {
	var i Printer

	//没有初始化的接口调用其方法会产生panic
	//panic: runtime error: invalid memory address or nil pointer dereference
	//i.Print()

	//必须初始化
	i = S{}
	i.Print()
}

type Printer interface {
	Print()
}

type S struct{}

func (s S) Print() {
	println("print")
}

4、接口的动态类型和静态类型

动态类型

接口绑定的具体实例的类型称为接口的动态类型。接口可以绑定不同类型的实例,所以接口的动态类型是随着其绑定的不同类型实例而发生变化的。

静态类型

接口被定义时,其类型就已经被确定,这个类型叫接口的静态类型。接口的静态类型在其定义时就被确定,静态类型的本质特征就是接口的方法签名集合。两个接口如果方法签名集合相同(方法的顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。原因是Go编译器校验接口是否能赋值,是比较二者的方法集,而不是看具体接口类型名。a接口的法集为A,b接口的法集为B,如果B是A的子集合,则a的接口变量可以直接赋值给B的接口变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值