什么是反射

维基百科上反射的定义:

在计算机科学中,反射是指计算机程序在运行时(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. dedecms织梦修改标题默认长度

    1 先在后台管理: 2 在数据库修改表dede_archives: ALTER TABLE `dede_archives` CHANGE `title` `title` VARCHAR( 250 ) ...

  2. Java基础系列(10)- 类型转换

    类型转换 由于Java是强类型语言,所以要进行有些运算的时候,需要用到类型转换.运算中,不同类型的数据先转换为同一类型,然后进行运算. 低 ------------------------------ ...

  3. Jmeter系列(24)- 常用逻辑控制器(3) | 模块控制器Module Controller

    模块控制器(Module Controller) 作用 可以理解为引用.调用执行的意思,调用范围为testplan树下任意的逻辑控制器,模块控制器除外 点开testplan树,需要引用哪个逻辑控制器选 ...

  4. markdown写作系统

    markdown 电脑本地使用typora,可保存为md文件直接上传到有道云笔记中 直接使用博客园做为图床,可以存为草稿,然后把复制连接 有道云笔记 粘贴博客园的图片连接

  5. python对象引用和垃圾回收

    变量="标签" 变量a和变量b引用同一个列表: >>> a = [1, 2, 3] >>> b = a >>> a.appen ...

  6. 鸿蒙内核源码分析(原子操作篇) | 是谁在为原子操作保驾护航 | 百篇博客分析OpenHarmony源码 | v34.02

    百篇博客系列篇.本篇为: v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航 | 51.c.h .o 本篇说清楚原子操作 读本篇之前建议先读鸿蒙内核源码分析(总目录)系列篇. 基 ...

  7. P4198-楼房重建【线段树】

    正题 题目链接:https://www.luogu.com.cn/problem/P4198 题目大意 \(n\)条线,开始时第\(i\)条是\((i,0)\)的一个点. 每次有操作把第\(x\)条线 ...

  8. 牛客挑战赛48E-速度即转发【带修莫队,分块】

    正题 题目链接:https://ac.nowcoder.com/acm/contest/11161/E 题目大意 给出\(n\)个数字的一个序列,\(m\)个操作. 给出\(l,r,k\),求一个最大 ...

  9. YbtOJ#723-欧拉之树【莫比乌斯反演,虚树】

    正题 题目链接:http://www.ybtoj.com.cn/contest/121/problem/2 题目大意 给出\(n\)个点的一棵树,每个点有一个权值\(a_i\),求 \[\sum_{i ...

  10. pytest环境准备与入门

    前言pytest 是 python2 默认自带的,python3 的版本 pytest 框架独立出来了,需用 pip 安装.以下为 python3.7版本为例 pytest是python的一种单元测试 ...