interface
是 Go 语言的基础特性之一,可以理解为一种类型的规范或者约定。
接口的声明
1 | // 空接口 |
接口指定了类型应该具有的方法,类型决定了如何实现这些方法。
1 | // 有方法列表的接口 |
接口的实现
Go语言中,接口的实现是隐式的。不用明确地指出某一个类型实现了某一个接口,只要在某一类型的方法中实现了接口中的全部方法签名,就意味着此类型实现了这一接口。
1 | type PeopleInface interface { |
接口动态类型
存储在接口变量中的类型称为接口的动态类型,而将接口本身的类型称为接口的静态类型
下面的代码中,sp
是接口变量,sp
的动态类型是student
,静态类型是speak
。sp
的静态类型永远是speak
,动态类型却会随着我们赋给它的动态值而变
1 | package main |
接口动态调用
当接口变量中存储了具体的动态类型时,可以调用接口中所有的方法。在对接口变量进行动态调用时,调用的方法只能是接口中具有的方法。
1 | type Speak interface { |
指针和接口
在实现接口的时候有两种方式,结构体实现和指针实现
《Go语言设计与实现》中的图
结构体类型和指针类型是不同的,在实现接口时这两种类型也不能划等号。虽然两种类型不同,但是上图中的两种实现不可以同时存在,Go 语言的编译器会在结构体类型和指针类型都实现一个方法时报错 “method redeclared”。
实现接口时可以选择接受者的类型,即结构体
或者结构体指针
,在初始化时也可以初始化成结构体
或者指针
。
在初始化成结构体
的时候,是可以调用接受者是结构体
和结构体指针
的方法。
在初始化成指针
的时候,只可以调用接受者是结构体指针
的方法。
……这特么的看代码吧!
1 | type Duck interface { |
至于为什么这样,在《Go语言设计与实现》中有这样的解释
两种接口的数据结构
Go 语言根据接口类型是否包含一组方法将接口类型分成了两类:
- 使用
runtime.iface
结构体表示包含方法的接口 - 使用
runtime.eface
结构体表示不包含任何方法的interface{}
类型
eface结构体
1 | type eface struct { // 16 字节 |
size
字段存储了类型占用的内存空间,为内存空间的分配提供信息;hash
字段能够帮助我们快速确定类型是否相等;equal
字段用于判断当前类型的多个对象是否相等,该字段是为了减少 Go 语言二进制包大小从typeAlg
结构体中迁移过来的
iface结构体
runtime.itab
结构体是接口类型的核心组成部分,每一个 runtime.itab
都占 32 字节,我们可以将其看成接口类型和具体类型的组合,它们分别用 inter
和 _type
两个字段表示:
1 | type itab struct { // 32 字节 |
hash
是对_type.hash
的拷贝,当我们想将interface
类型转换成具体类型时,可以使用该字段快速判断目标类型和具体类型runtime._type
是否一致fun
是一个动态大小的数组,它是一个用于动态派发的虚函数表,存储了一组函数指针。虽然该变量被声明成大小固定的数组,但是在使用时会通过原始指针获取其中的数据,所以fun
数组中保存的元素数量是不确定的