Go学习笔记07_(反射)

请注意,本文编写于 633 天前,最后修改于 191 天前,其中某些信息可能已经过时。

老马带你学习Go语言编程

初学笔记,如有错误.欢迎指正,不胜感激.

概念和作用

Reflection在计算机中表示程序能够检查自身结构的能力,他是元编程的一种形式.通过反射,可以在程序运行时,获取对象的类型信息,并可以利用这些类型信息做非常灵活的工作.

通过反射机制在程序运行时能够完成如下功能:

  • 确认对象类型
  • 确认对象的类型的所有成员变量和方法
  • 动态调用对象的方法

基本用法

本质上说,反射就是一种检查接口变量的类型和值得机制.在Go语言中reflect.Type表示变量的类型,reflect.Value表示变量的值.对任何变量进行发射,都可以得到一个包含Type和Value的类型信息结构.

反射第一定律:反射可以将 接口类型变量 转换成 反射类型对象
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
通过上面两个函数可以将 接口类型变量 即参数转换为反射类型对象 即返回值

反射第二定律:反射可以将 反射类型对象 转换为 接口类型变量
func (v Vale) Interface() (i interface{})
这两个定律互为逆操作.根据一个reflect.Value类型的变量,我们可以使用Interface()方法恢复其接口类型的值.

package main

import (
    "reflect"
    "fmt"
)

func main()  {
    var pi float64 = 3.1415926
    t := reflect.TypeOf(pi)
    v := reflect.ValueOf(pi)
    fmt.Println(t,v,&pi)
    pi = v.Interface().(float64)
    fmt.Println(pi,&pi)
}

上述函数使用了第一,第二定律,在转换前后打印的变量pi的地址都是一样的所以,在转换前后pi是没有变化的.

反射第三定律:如果要修改 反射类型对象 ,其值必须是可写的.
通过反射第一定律可知,反射对象包含了接口变量中存储的值以及类型.若反射对象中包含的值是原始值,那么可以通过反射对象修改原始值;如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的.

package main

import (
    "reflect"
    "fmt"
)

func main() {
    var num int = 1
    v := reflect.ValueOf(&num).Elem()
    v.SetInt(100)
    fmt.Println(v)
}

对结构体的反射

package main

import (
    "strconv"
    "reflect"
    "fmt"
)

type Person struct {
    Name string
    Age int
}

func (p *Person)SetName(name string) {
    p.Name = name
}

func (p *Person)SetAge(age int)  {
    p.Age = age
}

func (p *Person)ToString() string  {
    return "Name:"+p.Name+"Age:"+strconv.Itoa(p.Age)
}

func main() {

    p := &Person{"张三",20}
    v := reflect.ValueOf(p).Elem()
    count := v.NumField()
    for i := 0; i < count; i++ {
        fmt.Println(v.Type().Field(i).Name, v.Field(i).Type(), v.Field(i))
    }
    v.Field(0).SetString("李四")
    v.FieldByName("Age").SetInt(30)

    pv := reflect.ValueOf(&p).Elem()
    count = pv.NumMethod()
    fmt.Println(count)
    for i := 0; i < count; i++ {
        fmt.Println(pv.Type().Method(i).Name, pv.Type().Method(i).Type, pv.Type().Method(i))
    }
    parmas := make([]reflect.Value, 1)
    parmas[0] = reflect.ValueOf(29)
    pv.Method(0).Call(parmas)
    parmas[0] = reflect.ValueOf("王五")
    pv.MethodByName("SetName").Call(parmas)
    rv := pv.MethodByName("ToString").Call(nil)
    fmt.Println(rv[0])
    
}

上述代码定义一个Person结构体,并以Person指针为方法接收器定义了三个方法.

在主函数中,定义&Person实例,由于p已经是Person的指针了,所以在第一次转换为反射类型对象的时候直接使用了地址加解应用函数Elem(),获取.

使用v.NumField,取反射类型对象值中的字段数量,通过遍历打印字段名字,类型以及值.

然后可以通过Field(0)按照下标取字段,或者FieldByName("")字段名字取字段改变值.

获取反射类型对象的方法类似获取字段.

调用方法的时候由于使用Call()函数,而Call()函数中的参数为一个reflecct.Value类型的切片,所以定义slice,作为参数.而当没有参数的时候需要在Call()函数中传空值nil.调用哪一个方法类似取字段,亦可以使用下标或者名字的方式进行.

Comments

添加新评论