切片 go 去除第一个_Go 反射:根据类型创建对象-第二部分(复合类型)

这是关于 Golang 中根据类型创建对象系列博客的第二篇,讨论的是创建复合对象。第一篇在Go 反射:根据类型创建对象-第一部分(原始类型)

9af9a228436eb732ceef31bef0b1e598.png

在前一篇博客中,我解释了 go reflect 包 type 和 kind 的概念。这篇博客,我将深入探讨这些术语。因为相比原始类型,type 和 kind 对于复合类型来说含义更多。

类型和种类

“类型” 是程序员用来描述程序中数据和函数的元数据。type 在 Go 的运行时和编译器中有不同的含义。

可以通过一个例子来理解。例如有如下声明:

47e7ed73f446596a0356ea0ad5fd2fab.png

提到这个声明,程序员会说 Version 是一个 string 类型的变量。

考虑下面这个例子,Version 是一个复合类型。

4ee9b1c904c3bab7c432a6ff78eb8d7f.png

在这个例子中,第一行创建了一个新类型 VersionType。第二行,Version 被定义成一个 VersionType 类型的变量。这个类型(VersionType)的类型是 string,我们称之为 kind。

总结来说就是,Version 的 type 是 VersionType,Version 的 kind 是 string。

0ea6c3968fec7c37cda718cefdcd6088.png

运行时和编译器根据 Kind 来分别给变量和函数分配内存或栈空间。

根据原始种类来创建复合对象

创建 kind 为如下值的复合对象与根据原始种类创建原始对象并没有什么不同。

eb3f5c386b5f5849bb8bcf4da7ff2a35.png

下面是通过类型(VersionType)来创建 Version 的例子。因为 Version 有原始的 kind,故它可以使用零值来创建。

e5f8ed1325358001db1a1a2f8cc2792d.png

注意到一个 Type 类型的变量的 String() 方法将返回 Type 的全路径名称。例如, 如果 VersionType 定义在 mypkg 包中,String() 返回的值将为 mypkg.VersionType。

通过复合种类创建复合对象

复合种类是包括有其他种类的种类。Map,Struct,Array 等,都是复合种类。下面是复合种类的列表:

7c8811bc3792b8013b97f65a5b64a6ca.png

可以像原始对象一样使用零值来创建复合对象。但是,仅使用一个空值而不做其他额外的操作的话,它们并不能被初始化。下面一节将详细讨论如何初始化复合种类。

通过 type signature 来创建复合数组对象

一个空的数组对象可以通过零值来创建。一个数组的零值是一个空的数组对象。下面是通过数组的 type signature 来创建一个数组的例子:

17e6ea501bf9aeafc4c2ab8645cf15a6.png

该函数会创建一个包含一个任意空复合对象的 reflect.Value 类型的结构体。

reflect 包有一个 ArrayOf(int, Type) 函数,可以从来创建包含类型为 Type 的特定长度的数组,下面是一个示例:

480a8c32e9b04b7c6579adab8eda5443.png

数组中的元素类型取决于传递进来的参数 t。数组的长度决定于参数 length。要想使用 reflect 包将值提取进一个数组中,最好的办法是将数组作为接口处理。

098582163af3728d04396d62e5314b6e.png

注意 Slice() 方法也可以用来提取数组的值,但是该方法需要在计算 reflect.Value 之前先将数组转换为一个可寻址的数组。为了让你的代码更简洁和可读,最好还是是使用 Interface() 方法。

通过 type signature 来创建复合信道对象

一个空的信道对象可以通过零值来创建。一个信道的零值是一个空的信道对象。下面是通过信道的 type signature 来创建一个信道的例子:

1072f35df85d21cc63752e810c946626.png

该函数会创建一个包含一个任意空复合对象的 reflect.Value 类型的结构体。

reflect 包有两个方法来创建一个信道。ChanOf 是用来创建信道的 type signature,MakeChan(Type, int) 方法可以用来给信道分配内存。下面是一个例子:

13c1086b069557e929cebb57587485db.png

信道中元素的类型取决于传递进来的参数 t。信道的容量取决于参数 buffer。要想使用 reflect 包将值提取进一个信道中,最好的办法是将信道作为接口处理。信道的方向通过传入 ChanOf 的第一个参数来控制,可能的取值有:

e4fd2880a8ad09b86706c85f31740988.png

BothDir 表明信道既可读又可写。SendDir 表明信道只能写。RecvDir 表明信道只能读。

ba43bcc097a12cc15318094086297000.png

通过 type signature 来创建复合函数对象

函数对象不能使用零值来创建。

reflect 包有两个方法来创建函数。FuncOf 方法是用于创建函数的 type signature,MakeFunc(Type, func(args []Value) (results []Value)) Value 方法可以用来给函数分配内存。下面是一个例子:

a14cc94d8f40583e2cfd8cb70443225e.png

CreateFunc 有两个参数。第一个参数是你想创建的函数的 type,第二个是一个具体的函数,该函数实现了第一个参数的类型,但是其输入和输出都转换为了 reflect.Value 对象。我已经创建好了这个函数,所以关于如何动态创建函数你就不用再重新造轮子了。

为了使用它来创建函数,函数的 type signature 需要事先定义。比如:

2df4b021c6d1cfe387f73cf70574840e.png

描述函数 signature 的 Type 结构体可以通过下面方法来创建:

4c44dd2de61d5bf95d8202dd15821a0e.png

funcType 可以作为 CreateFunc 的第一个参数传入。满足 funcType 的 type signature 的函数可以作为第二个参数传入。比如:

e60ebdb87ed057860f15d154598faa26.png

函数 doubler 将输入值乘以 2,它接收一个 int 类型的参数并返回一个 int 类型的值。该函数满足 fn 类型,但是不满足通用的类型:

88903e5eef93af52a73052af8382d0c5.png

为了满足通用的类型,需要一个等价版的 doubler,如下所示:

bd021b8abbcecbf99be08eafddb46055.png

doublerReflect 在功能上相当于 doubler,它满足通用函数的 type signature。也就是说,它需要 1 个 reflect.Value 切片作为参数。并返回 1 个 reflect.Value 切片的值。输入表示函数的输入,返回值表示正在生成的新函数的返回值。

调用 CreateFunc 可以用 funcType 和 doublerReflect 作为参数。下面的示例显示了对新创建的函数的调用。

c04f4a0d0444be24ad638a0ecba6391e.png

通过 type signature 来创建复合映射对象

一个空的映射对象可以通过零值来创建。一个映射的零值是一个空的映射对象。下面是通过映射的 type signature 来创建一个映射的例子:

4606338db2175de06c274fd230a31308.png

该函数会创建一个包含一个任意空复合对象的 reflect.Value 类型的结构体。

reflect 包有一个 MapOf(Type, Type) 方法,该方法可以被用于创建映射,映射键的类型是传入的第一个 type ,值的类型为第二个 type。下面是一个示例:

b035355ea560a291175ae1a09390348a.png

要想使用 reflect 包将值提取进一个映射对象中,最好的办法是将映射作为接口处理。

7fec063b31c99e640ed26eb15091a020.png

注意到映射同样可以使用 MakeMapWithSize 来分配空间。使用该方法的步骤同上面一样,只是 MakeMap 可以用 MakeMapWithSize 来代替并且还需要传入一个大小的参数。

通过 type signature 来创建复合指针对象

一个空的指针对象可以通过零值来创建。一个指针的零值是一个 nil 的指针对象。下面是通过指针的 type signature 来创建一个指针的例子:

8045452d4590d3becb9868b958d85315.png

该函数会创建一个包含一个任意空复合对象的 reflect.Value 类型的结构体。

reflect 包有一个 PtrTo(Type) 方法,可以用于创建指向 Type 类型的指针。比如:

13496d3027eb589803740b1498f01963.png

注意到上面的功能同样可以使用 reflect.New(t) 来实现。

指针所指向的元素的类型决定于传入参数 t。要想使用 reflect 包将值提取进一个对象中,最好的办法是首先使用 Indirect() 或者 Elem() 方法来得到指针所指向对象的值,然后将该值作为接口处理。

d5f0c4f1dc64c33e3a26da5682150d67.png

通过 type signature 来创建复合切片对象

一个空的切片对象可以通过零值来创建。一个切片的零值是一个空的切片对象。下面是通过切片的 type signature 来创建一个切片对象的例子:

608d093ee7e40825056a681b96616cee.png

该函数会创建一个包含一个任意空复合对象的 reflect.Value 类型的结构体。

reflect 包有一个 SliceOf(Type) 的方法可以用于创建包含 Type 类型的切片。下面是使用方法:

5faf286458706bc1a4daa230a2dec3ac.png

切片中元素的类型由传入的参数 t 决定。要想使用 reflect 包将值提取进一个切片对象中,最好的办法是将该切片作为接口处理。

e8562b9a9ded74d4e0005a8a8f158b85.png

通过 type signature 来创建复合结构体对象

一个空的结构体对象可以通过零值来创建。一个结构体的零值是一个空的结构体对象。下面是通过结构体的 type signature 来创建一个结构体对象的例子:

d1e450a1650b7f4412ce716bfccdfdcb.png

该函数会创建一个包含一个任意空复合对象的 reflect.Value 类型的结构体。

reflect 包有一个 StructOf([]reflect.StructFields) 的方法可以用于创建包含 StructField 中定义的字段类型的结构体。下面是使用方法:

961349d5e2edaee18080f7d2adde9e7a.png

要想使用 reflect 包将值提取进一个结构体对象中,最好的办法是将该结构体作为接口处理。

7c5794b76cb401449c2acbadfc26def2.png

结论

这是一个关于使用 reflect 包来动态创建任意 Go 类型对象的完整教程。我提供了创建 Func 类型的便利方法,因为它比其他类型更复杂,如果不仔细设计,很容易污染您的代码库。 请继续关注我的下一篇博文,将任何类型转换为 Golang 中的其他类型!接下来,我将解释如何在 Golang 中编写 JIT (即时),然后介绍使用 reflect 来生成代码。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值