我们在之前已经在 函数调用 一节中介绍过,Go 语言的函数调用都是值传递的,变量会在方法调用前进行类型转换,也就是 int 类型的基本变量会被转换成 interface{} 类型,这也就是第一条法则介绍的是从接口到反射对象 。
第二法则
我们既然能够将接口类型的变量转换成反射对象类型,那么也需要一些其他方法将反射对象还原成成接口类型的变量,reflect 中的 Interface 方法就能完成这项工作:
文章插图
然而调用 Interface 方法我们也只能获得 interface{} 类型的接口变量,如果想要将其还原成原本的类型还需要经过一次强制的类型转换,如下所示:
【Go 语言反射的实现原理】?复制代码
v := reflect.ValueOf(1)v.Interface{}.(int)这个过程就像从接口值到反射对象的镜面过程一样,从接口值到反射对象需要经过从基本类型到接口类型的类型转换和从接口类型到反射对象类型的转换,反过来的话,所有的反射对象也都需要先转换成接口类型,再通过强制类型转换变成原始类型:
文章插图
当然不是所有的变量都需要类型转换这一过程,如果本身就是 interface{} 类型的,那么它其实并不需要经过类型转换,对于大多数的变量来说,类型转换这一过程很多时候都是隐式发生的,只有在我们需要将反射对象转换回基本类型时才需要做显示的转换操作 。
第三法则
Go 语言反射的最后一条法则是与值是否可以被更改相关的,如果我们想要更新一个 reflect.Value,那么它持有的值一定是可以被更新的,假设我们有以下代码:
?复制代码
func main() { i := 1 v := reflect.ValueOf(i) v.SetInt(10) fmt.Println(i)} $ go run reflect.gopanic: reflect: reflect.flag.mustBeAssignable using unaddressable value goroutine 1 [running]:reflect.flag.mustBeAssignableSlow(0x82, 0x1014c0) /usr/local/go/src/reflect/value.go:247 +0x180reflect.flag.mustBeAssignable(...) /usr/local/go/src/reflect/value.go:234reflect.Value.SetInt(0x100dc0, 0x414020, 0x82, 0x1840, 0xa, 0x0) /usr/local/go/src/reflect/value.go:1606 +0x40main.main() /tmp/sandbox590309925/prog.go:11 +0xe0运行上述代码时会导致程序 panic 并报出 reflect: reflect.flag.mustBeAssignable using unaddressable value 错误,仔细想一下其实能够发现出错的原因,Go 语言的 函数调用 都是传值的,所以我们得到的反射对象其实跟最开始的变量没有任何关系,没有任何变量持有复制出来的值,所以直接对它修改会导致崩溃 。
想要修改原有的变量我们只能通过如下所示的方法,首先通过 reflect.ValueOf 获取变量指针,然后通过 Elem 方法获取指针指向的变量并调用 SetInt 方法更新变量的值:
?复制代码
func main() { i := 1 v := reflect.ValueOf(&i) v.Elem().SetInt(10) fmt.Println(i)} $ go run reflect.go10这种获取指针对应的 reflect.Value 并通过 Elem 方法迂回的方式就能够获取到可以被设置的变量,这一复杂的过程主要也是因为 Go 语言的函数调用都是值传递的,我们可以将上述代码理解成:
?复制代码
func main() { i := 1 v := &i *v = 10}如果不能直接操作 i 变量修改其持有的值,我们就只能获取 i 变量所在地址并使用 *v 修改所在地址中存储的整数 。
实现原理
我们在上面的部分已经对 Go 语言中反射的三大法则进行了介绍,对于接口值和反射对象互相转换的操作和过程都有了一定的了解,接下来我们就深入研究反射的实现原理,分析 reflect 包提供的方法是如何获取接口值对应的反射类型和值、判断协议的实现以及方法调用的过程 。
类型和值
Golang 的 interface{} 类型在语言内部都是通过 emptyInterface 这个结体来表示的,其中包含一个 rtype 字段用于表示变量的类型以及一个 word 字段指向内部封装的数据:
?复制代码
type emptyInterface struct { typ *rtype word unsafe.Pointer}用于获取变量类型的 TypeOf 函数就是将传入的 i 变量强制转换成 emptyInterface 类型并获取其中存储的类型信息 rtype:
?复制代码
func TypeOf(i interface{}) Type { eface := *(*emptyInterface)(unsafe.Pointer(&i)) return toType(eface.typ)} func toType(t *rtype) Type { if t == nil { return nil } return t}rtype 就是一个实现了 Type 接口的接口体,我们能在 reflect 包中找到如下所示的 Name 方法帮助我们获取当前类型的名称等信息:
?复制代码
func (t *rtype) String() string { s := t.nameOff(t.str).name() if t.tflag&tflagExtraStar != 0 { return s[1:] } return s}
推荐阅读
- 推荐docker file这种方法 Docker3-Dockerfile创建镜像的方法
- 90行JS代码构建属于你的React
- 一个秒杀系统的设计思考,原来可以这么轻松的就学会了
- Mac上有效检测病毒的安全软件
- Python爬虫基础:验证码的爬取和识别详解
- 直播APP安卓端实现推送功能的方法原来这么简单
- 北京环球影城在什么地方 北京环球影城有存包的地方吗
- 煮粽子的水能喝吗 煮粽子的水可以煮饺子吗
- DDoS无解?网络攻击为什么花钱防不住?正确的解决方法了解一下?
- Python 实现docx文件的读写操作