Go:反射

参考:《Go语言圣经》
参考:深度解密Go语言之反射,实际这篇文章主要还是基于下面这个资料整理的,圣经没有讲到底层实现原理。

1、什么是反射

来自维基百科的解释:

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

来自《Go语言圣经》的解释

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


2、使用反射和不使用的理由

需要反射理由:

  • 有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
  • 有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。

少用反射理由:

  • 与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
  • Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接 panic,可能会造成严重的后果。
  • 反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。

3、反射的实现原理

当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。

1、types 和 interface

Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了的,比如 int, float64, []int 等等。注意,这个类型是声明时候的类型,不是底层数据类型。

反射主要与 interface{} 类型相关。底层结构如下:

	type iface struct {
	    tab  *itab
	    data unsafe.Pointer
	}
	
	type itab struct {
	    inter  *interfacetype
	    _type  *_type
	    link   *itab
	    hash   uint32
	    bad    bool
	    inhash bool
	    unused [2]byte
	    fun    [1]uintptr
	}

        其中 itab 由具体类型 _type 以及 interfacetype 组成。_type 表示具体类型,而 interfacetype 则表示具体类型实现的接口类型
        实际上,iface 描述的是非空接口,它包含方法;与之相对的是 eface,描述的是空接口,不包含任何方法,Go 语言里有的类型都 “实现了” 空接口。

	type eface struct {
	    _type *_type	//表示空接口所承载的具体的实体类型
	    data  unsafe.Pointer	//具体的值
}

2、接口变量声明赋值前后

        接口变量可以存储任何实现了接口定义的所有方法的变量。

        举例:Go 语言中最常见的Reader 和 Writer 接口:

	type Reader interface {
	    Read(p []byte) (n int, err error)
	}
	
	type Writer interface {
	    Write(p []byte) (n int, err error)
	}

        声明: 首先声明 r 的类型是 io.Reader,注意,这是 r 的静态类型,此时它的动态类型为 nil,并且它的动态值也是 nil。

        赋值: 之后,r = tty 这一语句,将 r 的动态类型变成 *os.File,动态值则变成非空,表示打开的文件对象。这时,r 可以用<value, type>对来表示为: <tty, *os.File>。图示如下:

	var r io.Reader
	tty, err := os.OpenFile("/Users/qcrao/Desktop/test", os.O_RDWR, 0)
	if err != nil {
	    return nil, err
	}
	r = tty

在这里插入图片描述

        断言: *os.File 还包含 Write 函数,也就是说 *os.File 其实还实现了 io.Writer 接口。断言语句可以执行:

	var w io.Writer
	w = r.(io.Writer)

        之所以用断言,而不能直接赋值,是因为 r 的静态类型是 io.Reader,并没有实现 io.Writer 接口。断言能否成功,看 r 的动态类型是否符合要求。
在这里插入图片描述
        空接口: 由于 empty 是一个空接口,因此所有的类型都实现了它,w 可以直接赋给它,不需要执行断言操作。

	var empty interface{}
	empty = w

在这里插入图片描述


4、反射的基本函数

1、TypeOf

        1、Type reflect.Type: 主要提供关于类型相关的信息,所以它和 _type 关联比较紧密;

        2、func TypeOf(i interface{}) Type :用来提取一个接口中值的类型信息。由于它的输入参数是一个空的 interface{},调用此函数时,实参会先被转化为 interface{}类型。这样,实参的类型信息、方法集、值信息都存储到 interface{} 变量里了。

        3、Type相关方法,主要与类型相关:

type Type interface {
	    // 所有的类型都可以调用下面这些函数
	
	    // 此类型的变量对齐后所占用的字节数
	    Align() int
	    
	    // 如果是 struct 的字段,对齐后占用的字节数
	    FieldAlign() int
	
	    // 返回类型方法集里的第 `i` (传入的参数)个方法
	    Method(int) Method
	
	    // 通过名称获取方法
	    MethodByName(string) (Method, bool)
	
	    // 获取类型方法集里导出的方法个数
	    NumMethod() int
	
	    // 类型名称
	    Name() string
	
	    // 返回类型所在的路径,如:encoding/base64
	    PkgPath() string
	
	    // 返回类型的大小,和 unsafe.Sizeof 功能类似
	    Size() uintptr
	
	    // 返回类型的字符串表示形式
	    String() string
	
	    // 返回类型的类型值
	    Kind() Kind
	
	    // 类型是否实现了接口 u
	    Implements(u Type) bool
	
	    // 是否可以赋值给 u
	    AssignableTo(u Type) bool
	
	    // 是否可以类型转换成 u
	    ConvertibleTo(u Type) bool
	
	    // 类型是否可以比较
	    Comparable() bool
	
	    // 下面这些函数只有特定类型可以调用
	    // 如:Key, Elem 两个方法就只能是 Map 类型才能调用
	    
	    // 类型所占据的位数
	    Bits() int
	
	    // 返回通道的方向,只能是 chan 类型调用
	    ChanDir() ChanDir
	
	    // 返回类型是否是可变参数,只能是 func 类型调用
	    // 比如 t 是类型 func(x int, y ... float64)
	    // 那么 t.IsVariadic() == true
	    IsVariadic() bool
	
	    // 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
	    Elem() Type
	
	    // 返回结构体类型的第 i 个字段,只能是结构体类型调用
	    // 如果 i 超过了总字段数,就会 panic
	    Field(i int) StructField
	
	    // 返回嵌套的结构体的字段
	    FieldByIndex(index []int) StructField
	
	    // 通过字段名称获取字段
	    FieldByName(name string) (StructField, bool)
	
	    // FieldByNameFunc returns the struct field with a name
	    // 返回名称符合 func 函数的字段
	    FieldByNameFunc(match func(string) bool) (StructField, bool)
	
	    // 获取函数类型的第 i 个参数的类型
	    In(i int) Type
	
	    // 返回 map 的 key 类型,只能由类型 map 调用
	    Key() Type
	
	    // 返回 Array 的长度,只能由类型 Array 调用
	    Len() int
	
	    // 返回类型字段的数量,只能由类型 Struct 调用
	    NumField() int
	
	    // 返回函数类型的输入参数个数
	    NumIn() int
	
	    // 返回函数类型的返回值个数
	    NumOut() int
	
	    // 返回函数类型的第 i 个值的类型
	    Out(i int) Type
	
	    // 返回类型结构体的相同部分
	    common() *rtype
	    
	    // 返回类型结构体的不同部分
	    uncommon() *uncommonType
	}

2、ValueOf()

        1、reflect.Value: 结合 _type 和 data 两者,因此程序员可以获取甚至改变类型的值。

        2、func ValueOf(i interface{}) Value: 返回值 reflect.Value 表示 interface{} 里存储的实际变量,它能提供实际变量的各种信息。

        3、Value相关函数,主要与取值设值相关:

	// 设置切片的 len 字段,如果类型不是切片,就会panic
	 func (v Value) SetLen(n int)
	 
	 // 设置切片的 cap 字段
	 func (v Value) SetCap(n int)
	 
	 // 设置字典的 kv
	 func (v Value) SetMapIndex(key, val Value)
	
	 // 返回切片、字符串、数组的索引 i 处的值
	 func (v Value) Index(i int) Value
	 
	 // 根据名称获取结构体的内部字段值
	 func (v Value) FieldByName(name string) Value
	 
	// 用来获取 int 类型的值
	func (v Value) Int() int64
	
	// 用来获取结构体字段(成员)数量
	func (v Value) NumField() int
	
	// 尝试向通道发送数据(不会阻塞)
	func (v Value) TrySend(x reflect.Value) bool
	
	// 通过参数列表 in 调用 v 值所代表的函数(或方法
	func (v Value) Call(in []Value) (r []Value) 
	
	// 调用变参长度可变的函数
	func (v Value) CallSlice(in []Value) []Value 

5、Type、Interface、Value互相转换

在这里插入图片描述


6、反射的实际运用

1、json 序列化

        两个函数的参数都包含 interface,具体实现的时候,都会用到反射相关的特性。

        对于序列化和反序列化函数,均需要知道参数的所有字段,包括字段类型和值,再调用相关的 get 函数或者 set 函数进行实际的操作。

	func Marshal(v interface{}) ([]byte, error)
	func Unmarshal(data []byte, v interface{}) error

2、DeepEqual 的作用及原理

        DeepEqual 函数的参数是两个 interface,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。
先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。

	func DeepEqual(x, y interface{}) bool

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

了-凡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值