从零开始学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引导
- 在接口定义中,只有方法声明没有方法实现
接口初始化
一个对象只要全部实现了接口中的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表,因此实现了该接口中所有的方法即实现了接口的初始化。
接口初始化的例子:
type Sayer interface {
say()
}
type dog struct {}
type cat struct {}
func (d dog) say() {
fmt.Println("汪汪汪")
}
func (c cat) say() {
fmt.Println("喵喵喵")
}
func main() {
var x Sayer
a := cat{}
b := dog{}
x = a
x.say()
x = b
x.say()
}
接口方法调用
接口方法调用的最终地址是在运行期决定的,将具体类型赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,节间调用实例的方法。接口方法的调用不是一种直接调用,有一定的运行时开销。
直接调用未初始化的接口变量会产生 panic
.
package main
type Printer interface {
Print()
}
type S struct{}
func (s S) Print() {
println("print")
}
func main() {
var i Printer
i = S{}
i.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
}
func (St) Ping() {
fmt.Println("Ping")
}
func (*St) Pang() {
fmt.Println("Pang")
}
func main() {
st := &St{"Tom"}
var i interface{} = st
o := i.(Inter)
o.Ping()
o.Ping()
s := i.(*St)
fmt.Printf("%s", s.Name)
}
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
}
func (St) Ping() {
fmt.Println("Ping")
}
func (*St) Pang() {
fmt.Println("Pang")
}
func main() {
st := &St{"Tom"}
var i interface{} = st
if o, ok := i.(Inter); ok {
o.Ping()
o.Pang()
}
if p, ok := i.(Anter); ok {
p.String()
}
if s, ok := i.(*St); ok {
fmt.Printf("%s", s.Name)
}
}
接口类型查询
接口类型查询语法:
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,没有意义
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)