Go语言学习笔记——反射

本文详细介绍了Go语言的反射机制,包括为何需要反射、reflect包的使用,特别是Type和Value接口的分析,以及如何通过反射操作接口值、修改反射对象、获取结构体标签等实例演示。
摘要由CSDN通过智能技术生成


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 "???"
    }
}

但是我们如何处理其它类似[]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
}

Value源码分析

type Value struct {
   
        // Has unexported fields.
}

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
零值的描述
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) []Value
反射函数的值,并调用

func (v Value) CallSlice(in []Value) []Value
同上

func (v Value) CanAddr() bool
检查v是否是可寻址的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值