Go语言入门-反射4(动态构建类型)

12.4 动态构建类型

反射技术不仅可以获取类型信息、执行类型成员,还可以动态创建类型。目前不支持动态创建的类型有:数组、切片、函数、结构体(仅包含字段,目前不支持生成方法成员)、通道(chan)、映射。

12.4.1 New 函数

New 函数创建指定类型的新实例,并以 Value 类型返回。不过,被创建的新实例实际是 *T 类型,假设调用 New 函数时指定的类型是 T,那么新实例的类型为 *T

若要获取指针所指向的对象,可以调用 Indirect 函数。该函数的源代码如下:

go 复制代码
func Indirect(v Value) Value {
    if v.Kind() == Ptr {
        return v.Elem()
    }
    return v
}

从上述源代码中可以看出,Indirect函数实际上是调用了 Value类型的Elem 方法。

下面是一个使用 New 函数的简单示例:

go 复制代码
var v1 = reflect.New(reflect.TypeOf(1))
fmt.Printf("v1 的类型: %v\n", v1.Type())

var v2 = reflect.New(reflect.TypeOf(struct {
    F1 uint
    F2 string
}{}))
fmt.Printf("v2 的类型: %v\n", v2.Type())

var v3 = reflect.New(reflect.TypeOf(func(string) {}))
fmt.Printf("v3 的类型: %v\n", v3.Type())

以下是示例的输出内容:

复制代码
v1 的类型: *int
v2 的类型: *struct { F1 uint; F2 string }
v3 的类型: *func(string)

不管调用 New 函数时指定的是什么类型,它返回的实例都是指针类型。

12.4.2 创建数组类型

在运行阶段动态创建数组类型,可以调用 ArrayOf 函数。它的签名如下:

go 复制代码
func ArrayOf(count int, elem Type) Type

count 参数用来指定数组的长度(即元素个数),elem 参数指定数组元素的类型。返回的 Type 对象表示新的数组类型。

例如,可以使用下面的代码生成元素类型为 string 的数组,数组长度为 5。

go 复制代码
//元素类型
var elemtp = reflect.TypeOf("xxx")
//构建数组类型
var arrType = reflect.ArrayOf(5, elemtp)

然后可以调用 New 函数创建数组实例,接着用类型化方法给数组的元素赋值。

go 复制代码
var arrVal = reflect.New(arrtype).Elem()

由于New函数创建的实例是指针类型,所以要调用Elem方法获取指针所指向的真实实例。

最后,可以给数组的元素赋值。

go 复制代码
// 给数组的元素赋值
arrVal.Index(0).SetString("Item_1")
arrVal.Index(1).SetString("Item_2")
arrVal.Index(2).SetString("Item_3")
arrVal.Index(3).SetString("Item_4")
arrVal.Index(4).SetString("Item_5")

调用 Interface 方法获取真实的数组实例,接着用类型断言将其转换为[5]string类型,并且枚举它的元素列表。

go 复制代码
theArr := arrVal.Interface().([5]string)
if ok{
    for i, item := range theArr {
        fmt.Printf("%d - %s\n", i, item) 
    }
}

枚举出来的元素如下:

复制代码
0 - Item_1
1 - Item_2
2 - Item_3
3 - Item_4
4 - Item_5

12.4.3 创建结构体类型

使用 StructOf 函数动态创建新的结构体类型,在调用该函数时候,需要提供一个StructField 列表。此列表包含了结构体的所有字段信息。在目前的Go语言版本中,只能为新的结构体类型添加字段,尚不支持生成方法列表。

StructOf 函数将忽略 StructField 对象的 OffsetIndex 字段的值,也就是说,结构体中的字段的位置和字节偏移量都由编译器决定。因此,在使用 StructField 对象的时候,不需要设置 OffsetIndex 字段的值,一般指定字段名称(Name)和字段类型(Type)即可。

在下面的示例中,动态创建了结构体类型,它包含四个字段,然后将其实例化,并为每个字段赋值。

步骤 1:准备需要的字段信息(四个字段)。

go 复制代码
var fds = []reflect.StructField{
    reflect.StructField{
        Name: "Header",
        Type: reflect.TypeOf("zzz"),// string
    },
    reflect.StructField{
        Name: "Size",
        Type: reflect.TypeOf(0), // int
    },
    reflect.StructField{
        Name: "Data",
        Type: reflect.TypeOf([]byte{}),
    },
    reflect.StructField{
        Name: "Position",
        Type: reflect.PtrTo(reflect.TypeOf(uint(0))), // *uint
    },
}

步骤 2:创建新结构体类型。

go 复制代码
newStruct := reflect.StructOf(fds)

步骤 3:创建新结构体类型的实例。

go 复制代码
structVal := reflect.New(newStruct).Elem()

New函数创建的实例是指针类型,所以要调用Elem方法获取指针所指向的真实实例。
步骤 4:设置结构体实例的字段值。

go 复制代码
structVal.Field(0).SetString("test") // Header
structVal.Field(1).SetInt(6)        // Size
structVal.Field(2).SetBytes([]byte("c2a5de")) // Data
var v uint = 60
structVal.Field(3).Set(reflect.ValueOf(&v)) // Position

步骤 5:本示例的运行结果如下:

复制代码
动态创建的结构体类型:
struct { Header string; Size int; Data []uint8; Position *uint }

结构体实例的值:
{Header:test Size:6 Data:[99 97 100 101 122] Position:0xc0000a00d0}

12.4.4 动态创建和调用函数

要动态创建新的函数类型,可以调用 FuncOf 函数。

go 复制代码
func FuncOf(in []Type, out []Type, variadic bool) Type

其中,in 表示函数的输入参数列表,out 表示的是函数的返回值(输出)列表。Variadic 参数是个布尔值,若为 true,则表示此函数所有输入参数的最后一个是可变参数;若为 false,则表示此函数没有可变参数。

FuncOf 函数只是创建函数类型(用Type表示),不包括函数体部分。要想创建函数实例,还需要调用 MakeFunc 函数。

go 复制代码
func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value

typ 指定函数的类型,fn 是一个匿名函数,用于构建函数体逻辑。当目标函数被调用时,目标函数的输入参数传递给 fn,而目标函数的返回将通过 fn 返回。

MakeFunc 函数调用成功后,返回引用函数实例(包含函数体)的 Value 对象,再使用 Value 对象的 Call 方法来调用目标函数。

在下面的示例中,首先动态创建一个函数类型:两个 float32 类型的输入参数,返回值为 string 类型。接着使用 MakeFunc 函数构建函数体,执行逻辑是将两个输入参数相减,再把结果以字符串形式返回。

步骤 1:本示例的运行结果如下:

go 复制代码
var (
    p1 = reflect.TypeOf(float32(0.01))
    p2 = reflect.TypeOf(float32(0.01))
    pins = []reflect.Type{p1, p2}
)

步骤 2:准备函数的返回值列表。

go 复制代码
var ret = reflect.TypeOf("")
var pouts = []reflect.Type{ret}

步骤 3:调用 FuncOf 函数,创建新的函数类型。

go 复制代码
myFunc := reflect.FuncOf(pins, pouts, false)

步骤 4:调用 MakeFunc 函数,构建函数体逻辑。

go 复制代码
funcInst := reflect.MakeFunc(myFunc, func(in []reflect.Value) []reflect.Value {
    // 取出两个参数的值
    f1 := in[0].Float()
    f2 := in[1].Float()
    // 将两个值相减
    fr := f1 - f2
    // 将结果转换为字符串
    str := fmt.Sprintf("%.2f", fr)
    // 封装返回值
    retv := reflect.ValueOf(str)
    return []reflect.Value{retv}
})

步骤 5:尝试调用函数。

go 复制代码
var input1, input2 float32 = 0.9, 0.7
cr := funcInst.Call([]reflect.Value{
    reflect.ValueOf(input1),
    reflect.ValueOf(input2),
})

步骤 6:向屏幕输出调用结果。

go 复制代码
var resval = cr[0].String()
fmt.Printf("输入参数: %.2f, %.2f\n", input1, input2)
fmt.Printf("函数调用结果: %s\n", resval)

步骤 7:运行代码,输出结果如下:

复制代码
刚创建的函数类型:
func(float32, float32) string
输入参数: 0.90, 0.70
函数调用结果: 0.20

在调用 FuncOf 函数时,如果 variadic 参数设置为 true,那么输入参数列表中的最后一个元素必须是切片类型。例如:

go 复制代码
// 第一个参数,类型:int
p1 := reflect.TypeOf(0)
// 第二个参数,类型:bool
p2 := reflect.TypeOf(true)
// 第三个参数
// 此参数个数可变,类型:[]string
p3 := reflect.TypeOf([]string{})

// 第一个返回值,类型:int
rt1 := reflect.TypeOf(0)
// 第二个返回值,类型:bool
rt2 := reflect.TypeOf(true)

// 构造输入/输出参数列表
pin := []reflect.Type{p1, p2, p3}
pout := []reflect.Type{rt1, rt2}

// 创建函数类型
t := reflect.FuncOf(pin, pout, true)

调用 FuncOf 函数后,产生的新函数类型为:

go 复制代码
func(int, bool,...string) (int, bool)

12.4.5 生成通用函数体

MakeFunc 函数生成的函数体逻辑实质上是一个函数实例,可以赋值给类型符合的函数变量。以下示例演示了使用 MakeFunc 函数生成这样的函数逻辑:函数接收两个参数,然后返回两个参数之和。

为了简化代码,本示例仅支持的参数/返回值类型为 int32int64float32float64,即

go 复制代码
func (int32, int32) int32
func (int64, int64) int64
func (float32, float32) float32
func (float64, float64) float64

首先,定义 initFunc 函数,该函数通过 iFn 参数接收函数变量的指针。接着使用 MakeFunc 函数生成函数实例。最后,将生成的函数实例赋值给 iFn 参数所指向的变量,即让函数变量引用新创建的函数实例。

go 复制代码
func initFunc(iFn interface{}) {
    var fnVal = reflect.ValueOf(iFn)
    if fnVal.Kind()!= reflect.Ptr {
        fmt.Println("请传递函数变量的内存地址")
        return
    }
    // 取得指针所指向的对象
    fnVal = fnVal.Elem()
    // 函数体逻辑
    var fnBody = func(ip []reflect.Value) (outs []reflect.Value) {
        outs = []reflect.Value{}
        if len(ip)!= 2 {
            fmt.Println("函数应该有两个参数")
            return
        }
        // 验证两个参数的类型是否相同
        if ip[0].Kind()!= ip[1].Kind() {
            fmt.Println("两个参数的类型不一致")
            return
        }
        // 取出两参数的值
        // 然后进行 "+" 运算
        var result interface{}
        switch ip[0].Kind() {
        case reflect.Int32:
            a := int32(ip[0].Int())
            b := int32(ip[1].Int())
            result = a + b
        case reflect.Int64:
            a := int64(ip[0].Int())
            b := int64(ip[1].Int())
            result = a + b
        case reflect.Float32:
            a := float32(ip[0].Float())
            b := float32(ip[1].Float())
            result = a + b
        case reflect.Float64:
            a := float64(ip[0].Float())
            b := float64(ip[1].Float())
            result = a + b
        default:
            result = nil
        }
        // 处理返回值
        resval := reflect.ValueOf(result)
        outs = append(outs, resval)
        return
    }
    // 构建函数实例
    funcInst := reflect.MakeFunc(fnVal.Type(), fnBody)
    // 让传递进来的函数变量引用函数实例
    fnVal.Set(funcInst)
}

再定义四个函数类型的变量。

go 复制代码
var (
    addInt32 func(int32, int32) int32
    addInt64 func(int64, int64) int64
    addFloat32 func(float32, float32) float32
    addFloat64 func(float64, float64) float64
)

用前面定义的 initFunc 函数分别给这些函数变量赋值(即初始化)。

go 复制代码
initFunc(&addInt32)
initFunc(&addInt64)
initFunc(&addFloat32)
initFunc(&addFloat64)

依次调用这四个函数。

go 复制代码
var a1, a2 int32 = 150, 25
r1 := addInt32(a1, a2)
fmt.Printf("%d + %d = %d\n", a1, a2, r1)

var b1, b2 int64 = 98900000, 45231002
r2 := addInt64(b1, b2)
fmt.Printf("%d + %d = %d\n", b1, b2, r2)

var c1, c2 float32 = 0.0021, 1.0099
r3 := addFloat32(c1, c2)
fmt.Printf("%f + %f = %f\n", c1, c2, r3)

var d1, d2 float64 = -770.00000542, 230.90005
r4 := addFloat64(d1, d2)
fmt.Printf("%f + %f = %f\n", d1, d2, r4)

调用结果如下:

复制代码
150 + 25 = 175
98900000 + 45231002 = 144131002
0.002100 + 1.009900 = 1.012000
-770.000005 + 230.900050 = -539.099955

总结

本文介绍了反射技术动态构建类型的相关内容,主要包括:

  1. New 函数:用于创建指定类型的新实例,返回 *T 类型。
  2. ArrayOf 函数:在运行阶段动态创建数组类型。
  3. StructOf 函数:动态创建新的结构体类型,但目前只能添加字段,不支持生成方法列表。
  4. FuncOf 函数:动态创建新的函数类型,MakeFunc 函数用于构建函数体逻辑。
  5. 生成通用函数体:通过 MakeFunc 函数生成函数实例,并赋值给符合类型的函数变量。
相关推荐
passionSnail2 分钟前
《MATLAB实战训练营:从入门到工业级应用》趣味入门篇-用声音合成玩音乐:MATLAB电子琴制作(超级趣味实践版)
开发语言·matlab
shenyan~15 分钟前
关于Python:9. 深入理解Python运行机制
开发语言·python
天堂的恶魔94618 分钟前
C++ - 仿 RabbitMQ 实现消息队列(1)(环境搭建)
开发语言·c++·rabbitmq
JH307325 分钟前
【SpringBoot】SpringBoot中使用AOP实现日志记录功能
java·spring boot·后端
吃货界的硬件攻城狮1 小时前
【STM32 学习笔记】EXTI外部中断
笔记·stm32·学习
殇淋狱陌1 小时前
【Python】常用命令提示符
开发语言·python·虚拟环境
anqi271 小时前
在sheel中运行Spark
大数据·开发语言·分布式·后端·spark
吃货界的硬件攻城狮1 小时前
【STM32 学习笔记 】OLED显示屏及Keil调试
笔记·stm32·学习
程序员小刚1 小时前
基于SpringBoot + Vue 的作业管理系统
vue.js·spring boot·后端
njsgcs1 小时前
chili3d调试笔记12 deepwiki viewport svg雪碧图 camera three.ts
笔记