• Go语言学习笔记——反射



    Golang反射

    反射简介

    Go语言提供了一种机制,能够在运行时更新变量和检查它们的值、调用它们的方法和它们支持的内在操作,而不需要在编译时就知道这些变量的具体类型。这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。

    即:反射可以在运行期间,操作任意类型的对象。可以通过TypeOf方法获得对象类型。通过ValueOf获得对象

    为何需要反射?

    有时候我们需要编写一个函数能够处理一类并不满足普通公共接口的类型的值,也可能是因为它们并没有确定的表示方式,或者是在我们设计该函数的时候这些类型可能还不存在。

    一个大家熟悉的例子是fmt.Fprintf函数提供的字符串格式化处理逻辑,它可以用来对任意类型的值格式化并打印,甚至支持用户自定义的类型。让我们也来尝试实现一个类似功能的函数。为了简单起见,我们的函数只接收一个参数,然后返回和fmt.Sprint类似的格式化后的字符串。我们实现的函数名也叫Sprint

    我们首先用switch类型分支来测试输入参数是否实现了String方法,如果是的话就调用该方法。然后继续增加类型测试分支,检查这个值的动态类型是否是stringintbool等基础类型,并在每种情况下执行相应的格式化操作。

    func Sprint(x interface{
       }) string {
       
        type stringer interface {
       
            String() string
        }
        switch x := x.(type) {
       
        case stringer:
            return x.String()
        case string:
            return x
        case int:
            return strconv.Itoa(x)
        // ...similar cases for int16, uint32, and so on...
        case bool:
            if x {
       
                return "true"
            }
            return "false"
        default:
            // array, chan, func, map, pointer, slice, struct
            return "???"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    但是我们如何处理其它类似[]float64map[string][]string等类型呢?我们当然可以添加更多的测试分支,但是这些组合类型的数目基本是无穷的。还有如何处理类似url.Values这样的具名类型呢?即使类型分支可以识别出底层的基础类型是map[string][]string,但是它并不匹配url.Values类型,因为它们是两种不同的类型,而且switch类型分支也不可能包含每个类似url.Values的类型,这会导致对这些库的依赖。

    没有办法来检查未知类型的表示方式,我们被卡住了。这就是我们为何需要反射的原因。

    reflect包

    反射是由 reflect 包提供的。 它定义了两个重要的类型, TypeValue. 任意接口值在反射中都可以理解为由 reflect.Typereflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOfreflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。

    Type接口源码分析

    源码:

    type Type interface {
       
    
    	// 内存字节对齐
    	Align() int
    
    	// 结构体内存字节对齐
    	FieldAlign() int
    
    	// 结构体实现的方法,必须是公共方法且首字母大写
    	Method(int) Method
    
    	//根据方法名获得方法
    	MethodByName(string) (Method, bool)
    
    	// 方法数量
    	NumMethod() int
    
    	// 类型名称
    	Name() string
    
    	// 包路径
    	PkgPath() string
    
    	// 需要存储的字节数
    	Size() uintptr
    
    	// 类型的字符串描述
    	String() string
    
    	// 返回此类型的具体种类
    	Kind() Kind
    
    	// 判断是否实现了某一个接口
    	Implements(u Type) bool
    
    	// 判断是否可以赋值给u
    	AssignableTo(u Type) bool
    
    	// 是否可以转换为u类型
    	ConvertibleTo(u Type) bool
    
    	// 是否可以比较
    	Comparable() bool
    
    	// 类型的字节长度
    	Bits() int
    
    	// 通道类型的方向
    	ChanDir() ChanDir
    	
    	// 函数类型是否是可变参数
    	IsVariadic() bool
        
    	// 返回类型的元素类型:Array, Chan, Map, Pointer, or Slice
    	Elem() Type
    
    	// 返回结构体的第几个字段
    	Field(i int) StructField
    
    	// 根据索引获得字段
    	FieldByIndex(index []int) StructField
    
    	// 根据名称获得字段
    	FieldByName(name string) (StructField, bool)
    
    	// 根据函数名称返回结构体字段
    	FieldByNameFunc(match func(string) bool) (StructField, bool)
    
    	// 函数类型的第几个参数
    	In(i int) Type
    
    	// 返回map类型的key
    	Key() Type
    
    	// 返回数组类型的长度
    	Len() int
    
    	// 返回结构体类型的字段数
    	NumField() int
    
    	// 返回函数类型的输入参数数量
    	NumIn() int
    
    	// 函数类型返回值的数量
    	NumOut() int
    
    	// 函数类型的第几个返回值
    	Out(i int) Type
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    Value源码分析

    type Value struct {
       
            // Has unexported fields.
    }
    
    • 1
    • 2
    • 3
    • 4

    Value 是 Go 值的反射接口。

    并非所有方法都适用于所有类型的值。每种方法的文档中都会注明限制条件(如果有)。

    在调用特定于种类的方法之前,使用 Kind 方法找出值的种类。调用不适合该类型的方法会导致运行时panic。

    • 零值表示没有值。

    • 它的 IsValid 方法返回 false,

    • 它的 Kind 方法返回 Invalid,

    • 它的 String 方法返回“< invalid Value >”,

    • 所有其他方法都panic。

    • 大多数函数和方法从不返回无效值。如果是这样,它的文档会明确说明条件。

    • 一个 Value 可以被多个 goroutine 同时使用,前提是底层的 Go 值可以同时用于等效的直接操作。

    • 要比较两个值,请比较 Interface 方法的结果。对两个值使用 == 不会比较它们所代表的基础值。

    value.go中的函数

    调用方法:reflect.XXX

    func Append(s Value, x ...Value) Value
    添加值到切片
    
    func AppendSlice(s, t Value) Value
    添加一个切片到切片
    
    func Indirect(v Value) Value
    返回v的指针值
    
    func MakeChan(typ Type, buffer int) Value
    创建一个通道,返回value类型
    
    func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value
    创建一个函数
    
    func MakeMap(typ Type) Value
    创建一个map
    
    func MakeMapWithSize(typ Type, n int) Value
    创建一个n大小的map
    
    func MakeSlice(typ Type, len, cap int) Value
    创建一个切片
    
    func New(typ Type) Value
    创建一个新的类型
    
    func NewAt(typ Type, p unsafe.Pointer) Value
    在某个指针地址处创建一个类型
    
    func ValueOf(i any) Value
    值类型
    
    func Zero(typ Type) Value
    零值的描述
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    Value结构体方法
    func (v Value) Addr() Value
    通常用于获取一个指向结构体字段或slice元素,为了调用一个方法需要一个指针receiver
    
    func (v Value) Bool() bool
    返回底层的值,如果v的kind不是bool则会panic
    
    func (v Value) Bytes() []byte
    返回底层的值,如果v的底层值不是一个字节切片则会panic
    
    func (v Value) Call(in []Value
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    Radius 身份认证 Java 客户端(FreeRADIUS)
    【软件设计师21天-考点整理】3)计算机系统构成及硬件基础知识
    C语言 变量
    Erlang 入门——从普通tcp到OTP框架通信
    为什么审计平台不适合进行数据库变更管理?
    如何使用GitHub托管代码(简易版)
    力扣(LeetCode)65. 有效数字(C++)
    【BOOST C++ 20 设计模式】(2)库Boost.Signals2
    国产8K摄像机记录中国航展的飞速发展
    前端如何控制并发请求数量?
  • 原文地址:https://blog.csdn.net/qq_39280718/article/details/126710379