美文网首页
GO基础学习(7)反射

GO基础学习(7)反射

作者: 温岭夹糕 | 来源:发表于2023-04-27 21:48 被阅读0次

写在开头

非原创,知识搬运工
demo代码地址

往期回顾

  1. 基本数据类型
  2. slice/map/array
  3. 结构体
  4. 接口
  5. nil
  6. 函数

带着问题去阅读

  1. 反射是把双刃剑,列举缺点
  2. 反射和接口的关系
  3. 反射的三大法则
  4. interface、type和value如何互相转化
  5. 列举反射应用场景
  6. 列举几个反射使用不当引发panic的场景

1.什么是反射

《GO语言圣经》对反射的描述是

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

也就是说它能让程序在运行时,能探知到一个对象的类型信息和内存结构,因此我们能联想到几个反射的应用场景:
1.当函数传入的参数为interface{}时(即没约定好),我们可以使用断言判断,也可以使用反射确定
2.根据某些条件(比如不确定的参数)决定调用哪个函数,可以根据反射来动态确定调用哪一个函数
总结来说就是在运行期间将不确定的因素转为确定(感觉很像断言的机制)

1.1反射的缺点

1.代码难阅读,不好维护
2.反射代码中的错误不容易被编译器发现,从而引发panic
3.反射性能差,运行速度慢

2.使用反射

2.1接口

反射与接口的结构,特别是空接口的结构体有很大的关联,我们复习下第4节接口的部分知识
接口分为两种类型:

  1. 带方法的接口,底层是iface
type iface struct {
    tab  *itab
    data unsafe.Pointer
}

type itab struct {
    inter *interfacetype
    _type *_type
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
其中_type表示的是实体类型,即具体类型,inter表示的是接口类型--由具体类型实现的接口类型,两者分别包含了接口和实现类的各种信息 image.png
  1. 空接口interface{} ,底层是eface
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

只有一个具体类型_type
当iface的动态值data和动态值_type都为nil时,接口==nil,iface的inter也叫做静态类型

var w io.Writer

io.Writer是一个接口,是w的静态类型
另外当函数的参数类型是interface{}时,会做一个隐式类型转换

2.2 反射与接口的关系

reflect包中定义了一个接口和一个结构体,即TypeValue

  • Type提供类型相关信息,与接口结构的_type关系紧密
  • Value则结合_type和data两者,因此我们可以获取甚至改变类型的值

2.2.1 Type

TypeOf是获取Type的方法

func TypeOf(i any) Type {
    eface := *(*emptyInterface)(unsafe.Pointer(&i))
    return toType(eface.typ)
}

//实际上就是类型转换
func toType(t *rtype) Type { 
    if t == nil {
        return nil
    }
    return t
}

注意1.18 参数类型是interface{},1.20是any实际是语法糖,emptyinterface和eface是一回事,字段相同,eface在reflect包中,emptyinterface在runtime包中

type emptyInterface struct{
    typ  *rtype
    word unsafe.Pointer
}

toType返回的 *rtype是一个实现了Type的接口,对Type调用方法实际上是对_type的操作

2.2.2Value

先看一下value的结构体

type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}
type flag uintptr

*rtype现在知道了吧,是一个Type接口的实现类即动态类型,ptr存放动态值
ValueOf获取Value

func ValueOf(i any) Value {
    if i == nil {
        return Value{}
    }

//内存操作,让值在堆中
    escapes(i)

    return unpackEface(i)
}

func unpackEface(i any) 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}
}

我们主要关心unpack函数:
1.转为eface
2.获取动态类型和动态值
3.组装数据
那么既然value有typ字段,说明value可以转为Type接口(具体实现方法就是Value.Type)

小结一下关系

image.png

这里我们对两者的其他方法不再过多分析

2.3三大法则

关于反射GO官方博客总结了三大法则

1.Reflection goes from interface value to reflection object.
2.Reflection goes from reflection object to interface value.
3.To modify a reflection object, the value must be settable.

1.反射是一种检测存储在interface{}中的类型和值机制。(通过TypeOf和ValueOf得到)
2.第二条与第一条相反,反射对象(ValueOf返回值)通过interface()函数反向转变成interface变量,也就是说接口和反射类型是可以互相转换
3.要修改反射对象,其值必须可以修改。如果反射变量可设置(因为反射变量存储了原变量本身),对反射变量的操作就会反应到原变量上;反之,如果不可设置,即反射变量不能代表原变量,那么操作反射变量不会对原变量产生影响,这种情况在语言层面是不能被允许的
demo

func TestChange(t *testing.T) {
    var x int64 = 1
    v := reflect.ValueOf(x)
    v.SetInt(2)
    t.Log(x)
}

这段代码编译器能通过,但实际上会引发panic(这也验证了上文反射代码错误能逃过编译器检查的观点)

panic: reflect: reflect.Value.SetInt using unaddressable value [recovered]
        panic: reflect: reflect.Value.SetInt using unaddressable value

提示了我们使用了一个不可寻址的值,我们在函数那一章提到了GO函数只有值传递,因此ValueOf传入的是一个变量的拷贝,ValueOf操作的是函数栈帧中的参数段,这里参数段与局部变量段没有联系,因此参数段不能代表局部变量段的x,不符合第三法则。
怎么解决?之前让函数参数段和局部变量段有关联,我们不就用了指针吗

v := reflect.ValueOf(&x)

需要注意的是此时传入的是一个地址,我们用Elem函数获取真正的x

    var x int64 = 1
    v := reflect.ValueOf(&x)
    // v := reflect.ValueOf(x)
    v.Elem().SetInt(2)
    t.Log(x)
//2

所以在使用反射时尽量使用指针(如果要操作变量)

2.4 结构体成员

正常情况下,结构体中可以被修改的成员只有导出成员,即首字母大写(表示结构体外可访问),对于未导出,只能读,不能修改,否则引发panic

3.reflect实际应用场景

IDE代码自动补全,对象序列化(json库),fmt相关函数,ORM等都涉及到反射reflect

相关文章

  • Go基础——反射

    reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值...

  • golang

    一、go基础 二、go应用 1 初级应用 1.1. 反射reflection 1.2. server服务 1.3....

  • go 基础学习

    1 go 基础go 语法基础go 官方资料如果由C ,C++ 基础, 学习go 比较容易,本文学习go ,主要是为...

  • reflect.go包学习_之二 指针操作提高反射性能 反射应用

    reflect.go包学习_之二 指针操作提高反射性能 反射应用 反射创建实例 反射信息、反射调用方法、反射修改值...

  • Go语言探索 - 12(结局)

    Go语言基础系列博客用到的所有示例代码 上一篇文章文章主要学习了Go语言中的接口、反射以及错误和异常处理。本篇文章...

  • Go语言基础——反射

    Go语言提供了一种机制,在编译时不知道类型的情况下,在运行时,可更新变量、查看值、调用方法以及直接对它的结构成员进...

  • go基础——反射二(反射使用)

    内容 1 获取接口类型值2 修改接口类型值3 反射调用函数4 反射调用方法5 reflect包api使用 先回顾一...

  • Golang 学习笔记十四 反射

    参考《快学 Go 语言》第 15 课 —— 反射反射是 Go 语言学习的一个难点,但也是非常重要的一个知识点。反射...

  • golang反射机制

    反射 反射就是程序能够在运行时动态的查看变量自己的所有属性和方法,能够调用他的任意方法和属性。 GO的反射基础是接...

  • go 反射

    反射的概念 反射就是程序能够在运行时动态的查看自己的状态,比关切允许修改自身的行为。1、GO的反射基础是接口和类型...

网友评论

      本文标题:GO基础学习(7)反射

      本文链接:https://www.haomeiwen.com/subject/krgpjdtx.html