Go设计与实现读书笔记-接口与反射

接口与反射

接口

从类型检查的过程来看, Go语言只会在传递参数、返回参数以及变量赋值时才会对某个类型是否实现接口进行检查, 类型实现接口时只需要实现接口中的全部方法, 不需要像java等编程语言一样显式声明.

Go语言使用runtime.iface表示带有一组方法的接口, 使用runtime.eface表示不带任何方法的interface{}.

type eface struct {
	_type *_type
	data unsafe.Pointer
}

type iface struct {
	tab *itab
	data unsafe.Pointer
}

runtime._type是Go语言类型的运行时表示, 包含了很多类型的元信息, 例如类型的大小、哈希、对齐以及种类等.

type itab struct {
	inter *interfacetype
	_type *_type
	hash uint32
	_ [4]byte
	fun [1]uintptr
}

itab结构体是接口类型和具体类型的组合, 分别用inter和_type两个字段表示. hash是对_type.hash的拷贝, 当我们想将interface类型转换为具体类型时, 可以使用该字段快速判断目标类型和具体类型是否一致. fun是一个动态大小的数组, 它是一个用于动态派发的虚函数表, 存储了一组函数指针.

类型转换

在实现接口时有指针类型和结构体类型两种方式, 会导致go语言编译器生成不同的汇编代码, 进而影响最终的处理过程.

  1. 指针类型
package main

type Duck interface {
	Quack()
}

type Cat struct {
	Name string
}

//go:noinline
func (c *Cat) Quack() {
	println(c.Name + " meow")
}

func main() {
	var c Duck = &Cat{Name: "draven"}
	c.Quack()
}

编译器将上述代码编译成汇编语言,将生成的汇编指令可以拆分成三部分:结构体Cat的初始化; 赋值出发的类型转换; 调用接口的方法Quack().

Cat的初始化过程:获取Cat结构体的类型指针, 并申请新的heap内存, 栈上的指针指向申请的这块堆内存.
类型转换: Duck的底层使用iface表示, 包含指向数据的指针和表示接口和结构体关系的tab字段共两个字段. Cat初始化过程中初始化了Cat的结构体指针, 只需要将编译期间生成的itab结构体复制到栈上.
调用接口方法: 编译期间会将一些需要动态派发的方法调用改写成对目标方法的直接调用, 以减少性能的额外开销.

  1. 结构体类型
package main

type Duck interface {
	Quack()
}

type Cat struct {
	Name string
}

//go:noinline
func (c Cat) Quack() {
	println(c.Name + " meow")
}

func main() {
	var c Duck = Cat{Name: "draven"}
	c.Quack()
}

如果我们在初始化变量时使用指针类型 &Cat{Name: “draven”} 也能够通过编译,不过生成的汇编代码和上一节中的几乎完全相同.

初始化结构体: 在栈上初始化结构体数据, 并且栈上生成一个指向结构体的指针;
类型转换: 将指向结构体的指针和itab传入convT2I函数, 申请一块堆内存, 并将结构体数据拷贝至内存, 并返回一个iface置于栈上.

使用结构体实现接口带来的开销会大于使用指针实现, 而动态派发在结构体上的表现非常差, 这也提醒我们应当尽量避免使用结构体类型实现接口;使用结构体带来的巨大性能差异不只是接口带来的问题, 带来性能问题主要是因为go语言在函数调用时是传值的, 动态派发的过程只是放大了参数拷贝带来的影响.

反射

go语言反射的三大法则:从interface{}变量可以反射出反射对象;从反射对象可以获取interface{}变量;要修改反射对象,其值必须可设置;

reflect.TypeOf的实现原理并不复杂,它只是将一个interface{}变量转换成了内部的reflect.emptyInterface表示, 然后从中获取相应的类型信息.

用于获取接口值reflcet.Value的函数reflect.ValueOf实现也非常简单,在该函数中我们先调用reflcet.escapes保证当前值逃逸到堆上,然后通过reflect.unpackEface从接口中获取reflect.Value结构体.reflcet.unpackEface会将传入的接口转换成reflect.emptyInterface,然后将具体类型和指针包装成reflect.Value结构体后返回.

reflect包还提供了reflect.rtype.Implements方法可以用于判断某些类型是否遵循特定的接口.

type CustomError struct{}

func (*CustomError) Error() string {
	return ""
}

func main() {
	typeOfError := reflect.TypeOf((*error)(nil)).Elem()
	customErrorPtr := reflect.TypeOf(&CustomError{})
	customError := reflect.TypeOf(CustomError{})

	fmt.Println(customErrorPtr.Implements(typeOfError)) // #=> true
	fmt.Println(customError.Implements(typeOfError)) // #=> false
}

找我内推!

百度】校招&社招&暑期实习内推,有意者评论区留言“内推”,可私聊沟通!咨询秒回!
工作地区:
北京、上海、广州、深圳、其他
招聘岗位
技术类: C++/Golang/PHP研发工程师、机器学习/数据挖掘/自然语言处理工程师、移动软件研发工程师、web前端研发工程师等
产品类: 产品经理、产品运营、商业产品经理、设计等方向
职能支持类: 销售营销、运营分析、战略投资、财务等
内推岗位达839个职位,持续更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uncle_mooncake

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值