什么是反射

维基百科上反射的定义:

在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。

我们知道一个变量在定义的时候是知道类型的,但是在运行中,可能会被随时转型,不管是显式还是隐式。

其本质就是程序在运行期探知对象的类型信息和内存结构。如果是汇编语言,我们完全没有这种烦恼。

其实什么是类型?只是确定如何解析这几段空间的0和1。

不同语言的反射模型不尽相同,有些语言还不支持反射。《Go 语言圣经》中是这样定义反射的:

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

反射的使用场景

其实Go比较尴尬的点在于没有泛型。所以用了interface这种来定义“不定参数”。而反射大多就是用来解析interface变量的。

但是通常反射的代码往往是有坑的:

  • 不好阅读。
  • 一旦有没判断好的地方,就容易panic。
  • 无法在编译的时候发现问题。
  • 运行速度较慢。

在这里还是期待Go的泛型早日出来。

Go是怎么实现反射的

想想如果我们,要随时知道这个变量的类型,我们会怎么做?

记下来呗。

Go也是一样,一个记录了taye,一个记录了值,当然这个值是一个指针,在需要获取值的时候,根据type执行对应的函数。

对外暴露了两个方法:

  1. func TypeOf(i interface{}) Type
  2. func ValueOf(i interface{}) Value
  1. func TypeOf(i interface{}) Type {
  2. eface := *(*emptyInterface)(unsafe.Pointer(&i))
  3. return toType(eface.type)
  4. }
  1. func ValueOf(i interface{}) Value {
  2. if i == nil {
  3. return Value{}
  4. }
  5. // ……
  6. return unpackEface(i)
  7. }
  8. // 分解 eface
  9. func unpackEface(i interface{}) Value {
  10. e := (*emptyInterface)(unsafe.Pointer(&i))
  11. t := e.typ
  12. if t == nil {
  13. return Value{}
  14. }
  15. f := flag(t.Kind())
  16. if ifaceIndir(t) {
  17. f |= flagIndir
  18. }
  19. return Value{t, e.word, f}
  20. }

将先将 i 转换成 *emptyInterface 类型, 再将它的 typ 字段和 word 字段以及一个标志位字段组装成一个 Value 结构体,而这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。

回到思路原点,其实就是返回了一个类型,一个地址,表达了如果去解析这段地址。

当然,这只是冰山一角,具体细节还望自行看源码。

反射的三大定律

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.

前两条是说可以通过反射对象得到interface变量,也可以将interface变量转化为反射对象。

  1. To modify a reflection object, the value must be settable.

这是说如果你要改变一个反射对象,它的值必须是可修改的。

举个栗子:

  1. var x float64 = 3.4
  2. v := reflect.ValueOf(x)
  3. v.SetFloat(7.1) // Error: will panic.

我们看valueOf的代码知道,它只是将传进去的变量的地址转为了emptyInterface,返回了一种解析方式,告诉你这个变量是什么类型,你应该如果解析它。

如果需要改变量,我们需要先的到这个变量的具体位置。

  1. func (v Value) Elem() Value {
  2. k := v.kind()
  3. switch k {
  4. case Interface:
  5. var eface interface{}
  6. if v.typ.NumMethod() == 0 {
  7. eface = *(*interface{})(v.ptr)
  8. } else {
  9. eface = (interface{})(*(*interface {
  10. M()
  11. })(v.ptr))
  12. }
  13. x := unpackEface(eface)
  14. if x.flag != 0 {
  15. x.flag |= v.flag.ro()
  16. }
  17. return x
  18. case Ptr:
  19. ptr := v.ptr
  20. if v.flag&flagIndir != 0 {
  21. ptr = *(*unsafe.Pointer)(ptr)
  22. }
  23. // The returned value's address is v's value.
  24. if ptr == nil {
  25. return Value{}
  26. }
  27. tt := (*ptrType)(unsafe.Pointer(v.typ))
  28. typ := tt.elem
  29. fl := v.flag&flagRO | flagIndir | flagAddr
  30. fl |= flag(typ.Kind())
  31. return Value{typ, ptr, fl}
  32. }
  33. panic(&ValueError{"reflect.Value.Elem", v.kind()})
  34. }

这里看到,要么interface,要么指针,其他的都会panic。

所以:

  1. var x float64 = 3.14
  2. v := reflect.ValueOf(x)
  3. fmt.Println("settability of v:", v.CanSet())
  4. v2 := reflect.ValueOf(&x)
  5. p := v2.Elem()
  6. fmt.Println("settability of p:", p.CanSet())

输出:

  1. settability of v: false
  2. settability of p: true

接语

看很多遍不如自己看一遍源码,其实很少的。

如果有不懂的还是欢迎评论,我都会回答的。

【Go语言细节】反射的更多相关文章

  1. C语言细节——献给入门者(三)

    C语言细节——献给入门者(三) >>主题:关于强制类型转换 先来瞎扯下强制类型转换,c语言有很多数据类型,long,short,int,float,double,bool,char等等.当 ...

  2. C语言细节——献给初学者(二)

    C语言细节——献给初学者(二) 主题  循环运用+选择判断 C语言循环有for和while/do...while: 选择判断有:if...else和switch...case 在循环中需要注意搭配br ...

  3. C语言细节——献给入门者(一)

    C语言细节——献给入门者(一) 主题  输入输出需要注意的细节 首先我们要知道大致有scanf(),printf(),getchar(),putchar(),gets(),puts()这几种输入方式. ...

  4. C语言细节总结笔记

    C语言细节总结笔记 */--> C语言细节总结笔记 Table of Contents 1. 三步异或法交换数字 2. 做差法交换数字 3. 按n位置位 4. 求余求商求积 5. 辗除法求最大公 ...

  5. go语言通过反射获取和设置结构体字段值的方法

    本文实例讲述了go语言通过反射获取和设置结构体字段值的方法.分享给大家供大家参考.具体实现方法如下: type MyStruct struct { N int } n := MyStruct{ 1 } ...

  6. Go语言之反射(一)

    反射 反射是指在程序运行期对程序本身进行访问和修改的能力.程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分.在运行程序时,程序无法获取自身的信息.支持反射的语言可以在程序编译期将 ...

  7. [转载] C语言细节,写的非常棒!

    这篇文章主要讨论C语言细节问题.在找一份工作的时候,语言细节占的比例非常小,之前看某个贴着讨论,估计语言细节在面试中,占了10%的比重都不到,那为什么还要研究C语言的细节呢,我觉得有三个原因促使我总结 ...

  8. 深度解密Go语言之反射

    目录 什么是反射 为什么要用反射 反射是如何实现的 types 和 interface 反射的基本函数 反射的三大定律 反射相关函数的使用 代码样例 未导出成员 反射的实际应用 json 序列化 De ...

  9. Go语言 8 反射

    文章由作者马志国在博客园的原创,若转载请于明显处标记出处:http://www.cnblogs.com/mazg/ Go学习群:415660935 8.1概念和作用 Reflection(反射)在计算 ...

随机推荐

  1. 带你掌握Vue过滤器filters及时间戳转换

    摘要:Vue的filters过滤器是比较常见的一个知识点,下面我将结合时间戳转换的例子带你快速了解filters的用法. 本文分享自华为云社区<三分钟掌握Vue过滤器filters及时间戳转换& ...

  2. pypandoc库实现文档转换

    写在前面: 对于python程序员来说,文件格式之间转换很常用,尤其是把我们爬虫爬到的内容转换成想要的文档格式时.这几天看到一个网站上有许多文章,个人很喜欢,直接复制太麻烦,为了将爬到的html文件以 ...

  3. command ' cl.exe' failed: No such file or directory解决办法

    1.安装C ++编译器 https://pan.baidu.com/s/1D1-tM-mWO4TVLdTrh3k1GA    提取码:ym67 2.找到安装文件夹:Visual C++ Build T ...

  4. django把变量变成字段进行搜索

    from ceshi.models import Student     #引入model中的模型 获取前端请求的参数 searchKey=request.GET.get("key" ...

  5. Windows与MAC使用差异有感(还会不断更新体验)

    Windows与MAC使用差异有感(还会不断更新体验) 关于键盘 这上是MAC与Windows的⌨️按键区别 我们现在都是USB键盘,而PS/2键盘是已经淘汰掉的(插头是圆孔的),看上图会发现Comm ...

  6. YbtOJ#752-最优分组【笛卡尔树,线段树】

    正题 题目链接:http://www.ybtoj.com.cn/problem/752 题目大意 \(n\)个人,每个人有\(c_i\)和\(d_i\)分别表示这个人所在的队伍的最少/最多人数. 然后 ...

  7. Redis新旧复制

    在Redis中,用户可以通过执行SALVEOF命令,让一个服务器去复制另一个服务器. 127.0.0.1:12345> SLAVEOF 127.0.0.1 6379 OK 6379的奴隶是123 ...

  8. 极简SpringBoot指南-Chapter04-基于SpringBoot的书籍管理Web服务

    仓库地址 w4ngzhen/springboot-simple-guide: This is a project that guides SpringBoot users to get started ...

  9. 面试官:为什么需要Java内存模型?

    面试官:今天想跟你聊聊Java内存模型,这块你了解过吗? 候选者:嗯,我简单说下我的理解吧.那我就从为什么要有Java内存模型开始讲起吧 面试官:开始你的表演吧. 候选者:那我先说下背景吧 候选者:1 ...

  10. BIBD&SBIBD的矩阵题

    证明不存在 \(01\) 方阵 \(A\) 使得: \(A^TA=\begin{pmatrix}7&2&\dots &2\\2&7&\dots&2\\ ...