Go:interface
interface 接口
go:interface{}、断言与类型转换
- Go的接口更大的作用是声明方法集合,而非类型约束。
- interface{}可用于向函数传递任意类型的变量,但对于函数内部,该变量仍然为interface{}类型(空接口类型),故必须进行类型断言确认类型后检查才能使用(不能直接隐式转换)。
- 接口类型向普通类型的转换称为类型断言(运行期确定)。
接口转换基本原则
- 普通类型 =》 接口类型:编译器运行时隐式转换。
- 接口类型 =》普通类型:必须显式类型断言。
- 超集和子集转换关系:超转子可以,子转超不可以。
断言推荐方式:
1 |
|
- 1.断言的作用:使用interface{}时,解决空接口类型向普通类型转换的类型转换问题;
- 2.普通类型之间的转换,使用显式的类型转换,否则后果严重不可控。
interface值传递注意事项:
- 如果接口实现方法,类型自己的实现使用的是值接收器,那么在传递值的时候无论使用指针还是值都可以。
- 如果接口实现方法,类型自己的实现使用的是指针接收器,那么在传递值的时候必须传递地址。
1
2
3
4
5原因:编译器不能自动获得一个未声明地址。
结构体类型定义的方法可以被该结构体的指针类型调用;而结构体类型调用该指针类型的方法时是被转换成指针,不是直接调用。
接口实现方法时,用指针类型实现的接口函数只能算是指针类型实现的,用结构体类型实现的方法也作为是指针类型实现。
interface{} 与 []interface{}
1 | var dataSlice []int = foo() |
编译错误
1 | cannot use dataSlice (type []int) as type []interface { } in assignment |
任何类型赋值给interface{},不能把任何类型的切片赋值到[]interface{}
不能 直接将某些[]MyType切片赋值给[]interface{}, 他们背后代表的数据意义不同。
1 | //编译错误 |
接口转换
- 利用类型推断,可判断接口对象是否某个具体的接口或类型。
- 还可用 switch 做批量类型判断,不支持 fallthrough。
- 超集接口对象可转换为子集接口,反之出错。
原因:
- []interface{}类型 不是 interface{}类型, 它是一个切片,切片元素的类型恰好是interface{}。
- []interface{}类型变量拥有特定的内存结构,这在编译时就已经决定。每个interface{}占两个字(word),一个字用于存放interface存放的类型,另一个字用于存放实际数据或者是指向数据的指针。于是长度为N的[]interface{}类型切片背后是一个N2字长的一块数据。
这与一般的[]MyType类型切片不同,相同长度的[]MyType切片背后的数据块大小为Nsizeof(MyType)字长。
使用方式:
如果想得到一个元素为任意类型的列表的容器,并且在索引其中元素之前会把它转换为原本的数据类型,可以直接使用interface{}即可。此种方式很通用(如果 不是编译时类型安全 的)也很快速。
接口类型内存布局(原理)
interface在内存上实际由两个成员组成
- tab指向虚表(Virtual Table)
- data则指向实际引用的数据。
- 虚表描绘了实际的类型信息及该接口所需要的方法集。
接口的底层结构
runtime.h
1 | struct Iface |
接口Demo:
1 | package main |
People接口本身,底层含有tab虚表和data实际存储的值两部分;
1 | **通过接口进行函数调用** ,实际的操作其实就是```p.tab->fun[0](p.data)```; |
type Tester interface {
Do()
}
type FuncDo func()
func (self FuncDo) Do() { self() }
func main() {
var t Tester = FuncDo(func() { println(“Hello, World!”) })
t.Do()
}
Go:interface