什么是反射

维基百科上反射的定义:

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

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

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

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

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

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

反射的使用场景

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

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

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

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

Go是怎么实现反射的

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

记下来呗。

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

对外暴露了两个方法:

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.type)
}
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// ……
return unpackEface(i)
}
// 分解 eface
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}

将先将 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.

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

举个栗子:

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

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

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

func (v Value) Elem() Value {
k := v.kind()
switch k {
case Interface:
var eface interface{}
if v.typ.NumMethod() == 0 {
eface = *(*interface{})(v.ptr)
} else {
eface = (interface{})(*(*interface {
M()
})(v.ptr))
}
x := unpackEface(eface)
if x.flag != 0 {
x.flag |= v.flag.ro()
}
return x
case Ptr:
ptr := v.ptr
if v.flag&flagIndir != 0 {
ptr = *(*unsafe.Pointer)(ptr)
}
// The returned value's address is v's value.
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
typ := tt.elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
return Value{typ, ptr, fl}
}
panic(&ValueError{"reflect.Value.Elem", v.kind()})
}

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

所以:

var x float64 = 3.14
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet()) v2 := reflect.ValueOf(&x)
p := v2.Elem()
fmt.Println("settability of p:", p.CanSet())

输出:

settability of v: false
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. Shell系列(11)- 位置参数变量(4)

    作用 往shell脚本里面传递参数 位置参数变量 作用 $n n 为数字,$0 代表命令本身,$1-$9 代表第一到第九个参数,十以上的参数需要用大括号包含,如 ${10} $* 这个变量代表命令行中 ...

  2. Python中“if __name__=='__main__':”

    在Python当中,如果代码写得规范一些,通常会写上一句"if name=='main:"作为程序的入口,但似乎没有这么一句代码,程序也能正常运行.这句代码多余吗?原理又在哪里? ...

  3. Maven项目创建与配置(二)

    项目配置 1:添加Source Folder 右击项目>NEW>Source Folder maven规定必须创建一下几个Source Folder src/main/resources ...

  4. django中admin一些方法

    1.概述:内容发布,负责添加,修改,删除内容及公告访问2.配置admin应用在settings文件中INSTALLED_APPS添加:'django.contrib.admin', 默认是已经添加好的 ...

  5. 『GoLang』协程与通道

    作为一门 21 世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算)和程序的并发.程序可以在不同的处理器和计算机上同时执行不同的代码段.Go 语言为构建并发程序的基本代码块是 ...

  6. keepalived-master-slave

    Master配置 global_defs { notification_email { root@localhost } notification_email_from keeplived@local ...

  7. C#开发BIMFACE系列48 Nginx部署并加载离线数据包

    BIMFACE二次开发系列目录     [已更新最新开发文章,点击查看详细] 在前一篇博客<C#开发BIMFACE系列47 IIS部署并加载离线数据包>中详细介绍了IIS部署并访问的完整步 ...

  8. vue 2.0源码学习笔记—new Vue ( Vue 初始化过程 )

    new Vue(Vue 初始化) 一个vue实例化到底经历了什么?已下是博主自己的总结,不正确的地方请指出,谢谢~ 一.简述 从使用角度来看,挂载的顺序如下 1. $slots 2. $scopedS ...

  9. perl合并文件

    使用Perl合并文件 有时需要将整个目录下的小文件合并到一个文件中,以便查阅检索 特性 整个目录完全遍历,自动存入单个文件顺序遍历文件 待合并的目录 合并后的文件内容 syscfg/test1 sys ...

  10. 脚本注入1(boolean&&get)

    现在,我们回到之前,练习脚本支持的布尔盲注(get型). 布尔盲注的应用场景是查询成功和失败时回显不同,且存在注入点的地方. 这里以Less-8为例: 发现查询成功时,会显示:失败则无回显. 同时发现 ...