接口
Go 的 interface 是实现鸭子类型编程的关键。只要类型实现了接口要求的方法集,就可以赋值给该接口变量。 接口变量可以存储“任何实现了该接口的对象”。
接口的底层类型
空接口(interface{})结构
type eface struct {
_type *_type // 这个值实际存储目标对象的类型信息指针
data unsafe.Pointer // 指向目标对象的指针
}
_type 记录对象的类型信息。比如你放进去一个int、string,都会有一个 *_type 指针。 data 直接指向对象数据。
非空接口(有方法的接口,如 io.Reader)
type iface struct {
tab *itab // 指向方法集和类型信息的表(虚表)
data unsafe.Pointer // 指向目标对象的数据
}
itab : 包含类型信息、接口方法对应的函数指针表(类似C++虚表)、动态类型等。 itab 结构如下(简化):
type itab struct {
inter *interfacetype // 该接口类型信息
_type *_type // 实际对象的类型信息
hash uint32
fun [N]uintptr // 函数指针表,N为接口方法个数
}
其实就是把”对象说是啥接口”、“实际类型”以及“接口方法的跳转表”等全记下来了。
接口赋值的原理
举例:
Go
type Foo struct{}
func (Foo) Bar() {}
var i MyInterface = Foo{}
过程: Go 编译器查找实现了 MyInterface 的所有方法。 找到 Foo 类型的方法,创建 itab 对象(或复用)。 把类型信息指针、方法表指针存进 interface 变量。
类型断言和底层原理
示例:
Go
if v, ok := i.(*MyStruct); ok {
// ...
}
编译器做了什么?
拿interface里的_type和被断言类型比对,能匹配则类型断言成功,同时断言值v=(*MyStruct)(data) Go反射(reflect)包就是操作这些底层结构
反射底层使用eface
type T struct{ X int }
v := T{11}
var a interface{} = v
rv := reflect.ValueOf(a)
fmt.Println(rv.Kind()) // struct
fmt.Println(rv.Field(0).Int()) // 11
reflect.ValueOf(a)本质:a是eface,底层取出_type(就是T的类型描述),data(T的实例数据) Value对象直接复用这两个指针 查询字段,就是拿类型描述_type里的结构体元信息,加上data内存偏移读取数据
接口组合
接口组合,就是一个接口可以嵌入(组合)其他接口,从而“拼”出一个更大、更复杂的接口类型。
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
工作原理 接口的“方法集”是所有组合接口方法的并集 实现了“全部方法”的类型,自动实现组合接口 这是一种“类型约定”而不是“继承”
接口组合是Go语言解耦、复用和“模拟多继承”的利器,核心在于“将若干接口的方法集合并成大接口,实现了所有方法的类型自动实现组合接口”。这让代码更松耦合、扩展性更强。
文档信息
- 本文作者:Beast
- 本文链接:https://beastpu.github.io/2023/06/10/go-%E6%8E%A5%E5%8F%A3/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)