Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。

    因而一直想的是自己可以根据自己学习和使用Go语言编程的心得,写一本Go的书可以帮助想要学习Go语言的初学者快速入门开发和使用!

    反射(reflection)

    在运行时反射是程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。它同时也是造成迷惑的来源。反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法。这对于没有源代码的包尤其有用。

    golang实现反射是通过reflect包来实现的, 让原本是静态类型的go具备了很多动态类型语言的特征。reflect包有两个数据类型,一个是Type,一个是Value。它定义了两个重要的类型,Type和Value. Type就是定义的类型的一个数据类型,Value是值的类型, TypeOf和ValueOf是获取Type和Value的方法。一个Type表示一个Go类型.它是一个接口, 有许多方法来区分类型以及检查它们的组成部分, 例如一个结构体的成员或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息,也正是这个实体标识了接口值的动态类型.

    接着我们开始我们使用Golang反射,通常在使用到Golang反射的时候会有三种定律:

    • 反射定律一:反射可以将“接口类型变量”转换为“反射类型对象”.

    这里的反射类型指的是reflect.Type和reflect.Value.将接口类型变量转换为反射类型变量,是通过reflect包的TypeOf和ValueOf实现的。

    下面我们来看看在reflect包中的TypeOf和ValueOf的定义:

    TypeOf

    1. # ValueOf返回一个新的Value,初始化为存储在接口i中的具体值。 ValueOf(nil)返回零值
    2. func ValueOf(i interface{}) Value

    然后我们可以使用reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型:

    1. var p int = 10
    2. v1 := reflect.ValueOf(p)//返回Value类型对象,值为10
    3. t1 := reflect.TypeOf(p)//返回Type类型对象,值为int
    4. fmt.Println("v1:",v1)
    5. fmt.Println("t1",t1)
    6. v2 := reflect.ValueOf(&p)//返回Value类型对象,值为&p,变量p的地址
    7. t2 := reflect.TypeOf(&p)//返回Type类型对象,值为*int
    8. fmt.Println("v2:",v2)
    9. fmt.Println("t2:",t2)

    运行结果:

    1. v1: 10
    2. t1: int
    3. v2: 0xc4200180b8
    4. t2: *int

    其中v1和v2中包含了接口中的实际值,t1和t2中包含了接口中的实际类型.由于reflect.ValueOf和reflect.TypeOf的参数类型都是interface{},空接口类型,而返回值的类型是reflect.Value和reflect.Type,中间的转换由reflect包来实现。所以就实现了接口类型变量到反射类型对象的转换.

    这里根据一个 reflect.Value类型的变量,我们可以使用Interface方法恢复其接口类型的值。事实上,这个方法会把 type和value信息打包并填充到一个接口变量中,然后返回.

    下面我们来看看在reflect包中Value的定义:

    然后我们可以使用Value将反射类型转换为接口类型变量:

    1. var a int = 10
    2. t1 := reflect.TypeOf(&a) //返回Type类型对象,值为*int
    3. fmt.Println("v1:",v1)
    4. fmt.Println("t1:",t1)
    5. v2 := v1.Interface() //返回空接口变量
    6. v3 := v2.(*int) //类型断言,断定v1中type=*int
    7. fmt.Printf("%T %v\n", v3, v3)
    8. fmt.Println("v3:",*v3)

    运行的结果:

    1. v1: 0xc420082010
    2. t1: *int
    3. *int 0xc420082010
    4. v3: 10

    reflect.ValueOf 的逆操作是 reflect.Value.Interface方法.它返回一个 interface{}类型,装载着与reflect.Value相同的具体值,这样我们就可以将“反射类型对象”转换为“接口类型变量.

    • 反射定律三:如果要修改反射类型对象,其值必须是“addressable” 在上面第一种反射定律将“接口类型变量”转换为“反射类型对象”我们可以知道,反射对象包含了接口变量中存储的值以及类型。如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值,如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的。

    那么我们可以通过CanSet函数可以判定反射对象是否可以修改。

    1. # CanSet reports whether the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields. If CanSet returns false, calling Set or any type-specific setter (e.g., SetBool, SetInt) will panic.
    2. # CanSet报告是否可以更改v的值.仅当值可寻址且未通过使用未导出的struct字段获取时,才能更改值。如果CanSet返回false,则调用Set或任何特定于类型的setter(例如,SetBool,SetInt)将会发生混乱。

    注意:CanSet返回false,值是不可以修改的,如果返回true则是可以修改的.

    运行结果:

    1. CanSet return bool: true
    2. p= 6.1

    从运行结果看值修改成功了,但是这里出现了Elem函数作用是用来获取原始值对应的反射对象.

    1. # Elem返回接口v包含的值或指针v指向的值。如果v的Kind不是Interface或Ptr,它会发生恐慌。如果v为nil,则返回零值。
    2. func (v Value) Elem() Value

    在这里要修改变量p的值,首先就要通过reflect.ValueOf来获取p的值类型,refelct.ValueOf返回的值类型是变量p一份值拷贝,要修改变量p就要传递p的地址,从而返回p的地址对象,才可以进行对p变量值对修改操作。在得到p变量的地址值类型之后先调用Elem()返回地址指针指向的值的Value封装。然后通过Set方法进行修改赋值。

    通过反射可以很容易的修改变量的值,我们首先要通过反射拿到这个字段的地址值类型,然后去判断反射返回类型是否为reflect.Ptr指针类型(通过指针才能操作对象地址中的值)同时还要判断这个元素是否可以修改,通过Kind()和Set来修改字段的值,然后就可以拿到我们修改的值了.

    因此在反射中使用反射包提供refelct.TypeOf和refelct.ValueOf方法获得接口对象的类型,值和方法等。通过反射修改字段值等时候需要传入地址类型,并且需要检查反射返回值类型是否为refelct.Ptr,检查字段是否CanSet,检查字段是存在,然后通过Kind()来帮助赋值相应对类型值。

    应用示例:

    1. package main
    2. import (
    3. "fmt"
    4. "reflect"
    5. )
    6. type User struct {
    7. Name string `json:"name"`
    8. Gender string `json:"gender"`
    9. Age int `json:"age"`
    10. }
    11. func main() {
    12. types := reflect.TypeOf(&User{}).Elem()
    13. value := reflect.ValueOf(&User{}).Elem()
    14. fmt.Println("values Numfield:",value.NumField())
    15. for i:=0;i<types.NumField();i++{
    16. m := types.Field(i).Tag.Get("json")
    17. fmt.Println(m)
    18. }