从零开始学GO ---- 接口
接口是一个编程规范,一组方法签名的集合。Go的接口是非侵入式的设置,一个具体类型实现接口不需要在语法上显式地声明,只要具体类型的方法集是接口方法集的超集,就代表该类型实现了接口,编译器在编译时进行方法的校验。接口定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。
在Go语言中接口(interface)是一种类型,一种抽象的类型。
接口声明
Go语言接口分为 接口字面变量类型和接口命名类型,用interface
关键字声明
接口字面变量类型的声明语法:
interface{
MethodSignature1
MethodSignature2
}
接口命名类型使用type
关键字声明:
type InterfaceName interface{
MethodSignature1
MethodSignature2
}
接口定义的大括号内是方法声明的集合,也可以嵌入另一个接口类型匿名字段。
例如:
type Reader interface{
Read(p []byte) (n int,err error)
}
type Writer interface{
Writer(p []byte)(n int,err error)
}
type ReadWriter interface{
Reader
Writer
}
//《====》等价于
type ReadWriter interface{
Read(p []byte)(n int,err error)
Writer(p []byte)(n int,err error)
}
声明新接口类型的特定:
- 接口的命名一般以"er"结尾
- 接口定义的内部方法不需要func引导
- 在接口定义中,只有方法声明没有方法实现
接口初始化
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表,因此实现了该接口中所有的方法即实现了接口的初始化。
接口初始化的例子:
// Sayer 接口
type Sayer interface {
say()
}
//dog和cat接口体
type dog struct {}
type cat struct {}
//因为Sayer接口里只有一个say方法,所以只需要给dog和cat 分别实现say方法就可以实现Sayer接口了。
// dog实现了Sayer接口
func (d dog) say() {
fmt.Println("汪汪汪")
}
// cat实现了Sayer接口
func (c cat) say() {
fmt.Println("喵喵喵")
}
func main() {
var x Sayer // 声明一个Sayer类型的变量x
a := cat{} // 实例化一个cat
b := dog{} // 实例化一个dog
//由于cat和dog都实现了Sayer接口,因此Sayer接口变量可以被赋值为cat和dog
x = a // 将cat实例直接赋值给x
x.say() // 喵喵喵
x = b // 将dog实例直接赋值给x
x.say() // 汪汪汪
}
接口方法调用
接口方法调用的最终地址是在运行期决定的,将具体类型赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,节间调用实例的方法。接口方法的调用不是一种直接调用,有一定的运行时开销。
直接调用未初始化的接口变量会产生 panic
.
package main
type Printer interface {
Print()
}
type S struct{}
func (s S) Print() {
println("print")
}
func main() {
var i Printer
//未初始化的接口调用方法会产生panic
//i.Print()
//
i = S{} //进行初始化
i.Print() //运行时简介调用S.Print
//print
}
动态类型和静态类型
动态类型: 接口绑定的具体实例的类型称为接口的动态类型,动态类型随着不同的绑定会发生变化
静态类型: 接口被定义时,其类型就完全被确定,这个类型就是接口的静态类型,静态类型本质特征是接口方法签名集合,两个接口如果方法集合相同的话则静态类型一致可以相互赋值。如果a接口的方法集A,b接口的方法集B,其实B是A的子集合,则a的接口变量可以直接赋值给b的接口变量。
接口运算
接口类型断言
接口断言的语法形式:i.(TypeName)
i
是接口变量,TypeName
可以是接口类型名,也可以是具体类型名
接口查询的两层语义:
- 如果
TypeName
是一个具体类型名,则类型断言用来判断接口变量i
绑定的实例类型是否就是具体类型TypeName
- 如果
TypeName
是一个接口类型名,则类型断言可以判断接口变量i
绑定的实例类型是否同时实现了TypeName
接口
接口断言的两种语法表现:
直接赋值模式:
o:=i.(TypeName)
- 如果
Typename
是具体类型名,并且接口i
绑定的实例类型就是具体类型TypeName
,则变量o
的类型就是TypeName
,变量o
的值是接口绑定的实例值的副本 - 如果
Typename
是接口类型名,如果接口i
绑定的实例类型满足接口类型TypeName
,则变量o
的类型就是TypeName
,变量o
底层绑定的具体类型实例就是i
绑定的实例的副本 - 否则,程序抛出
panic
package main
import "fmt"
//实现一个内部接口
type Inter interface {
Ping()
Pang()
}
//实现一个外部接口
type Anter interface {
Inter //内部接口
String()
}
type St struct {
Name string
}
//为 St实现Inter的接口方法集
func (St) Ping() {
fmt.Println("Ping")
}
func (*St) Pang() {
fmt.Println("Pang")
}
func main() {
st := &St{"Tom"}
var i interface{} = st //定义一个空接口绑定到st
o := i.(Inter) //判断i绑定的实例是否实现了接口类型Inter
o.Ping() //Ping
o.Ping() //Pang
//p := i.(Anter) //判断i绑定的实例是否实现了接口类型Anter
//p.String()
//panic: interface conversion: *main.St is not main.Anter: missing method String
//因为i没有实现String故没有实现Anter,会引发panic
s := i.(*St) //判断i绑定的实例是否就是具体类型St
fmt.Printf("%s", s.Name) //Tom
}
comma, ok 表达式模式:
if o,ok:=i.(TypeName);ok{ }
TypeName
是具体类型名,接口i
绑定的实例类型就是具体类型TypeName
,则ok
为true
,变量o
类型就是TypeName
,变量o
的值就是接口绑定的实例值的副本TypeName
是接口类型名,接口i
绑定的实例类型满足接口类型TypeName
,则ok
为true
,变量o
类型就是接口类型TypeName
,变量o
底层绑定的具体类型是i
绑定的实例的副本- 否则,
ok
为false
,变量o
是TypeName
类型的零值,此种条件下的分支逻辑不应该再去引用o
package main
import "fmt"
//实现一个内部接口
type Inter interface {
Ping()
Pang()
}
//实现一个外部接口
type Anter interface {
Inter //内部接口
String()
}
type St struct {
Name string
}
//为 St实现Inter的接口方法集
func (St) Ping() {
fmt.Println("Ping")
}
func (*St) Pang() {
fmt.Println("Pang")
}
func main() {
st := &St{"Tom"}
var i interface{} = st //定义一个空接口绑定到st
if o, ok := i.(Inter); ok { //判断i绑定的实例是否实现了接口类型Inter
o.Ping() //Ping
o.Pang() //Pang
}
if p, ok := i.(Anter); ok {
//由于i没有实现接口Anter,故ok为false,程序不执行到这里
p.String()
}
if s, ok := i.(*St); ok {
fmt.Printf("%s", s.Name) //Tom
}
}
接口类型查询
接口类型查询语法:
switch v:=i.(type){
case type1:
xxxx
case type2:
xxxx
default:
xxxx
}
i
是一个接口类型case
后可以接接口类型名,也可以接 非接口类型名- case 接口类型名,如果
i
绑定的实例类型实现了该接口类型的方法则匹配成功,v的类型是接口类型,v底层绑定的实例是i绑定具体类型实例的副本 - case 具体类型名,如果接口变量i绑定的实例类型和该具体类型相同,则匹配成功,此时v就是该具体类型变量,v的值是i绑定的实例值的副本
- 均不匹配的话执行default,v的值为0,没有意义
- case 接口类型名,如果