Go语言基础 – 反射

  • A+

反射介绍

反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go程序在运行期使用reflect包访问程序的反射信息。

reflect包

在Go语言的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的,在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Type和reflect.Value两部分组成,并且reflect包提供了reflect.TypeOf和reflect.ValueOf两个函数来获取任意对象的Value和Type。

reflect包方法说明

import "reflect"       // 导入包

1.reflect.TypeOf():获取变量的类型,返回reflect.type类型

2.reflect.ValueOf():获取变量的值,返回reflect.Value类型

3.reflect.Value.Kind:获取变量的类别,返回一个常量

4.reflect.Value.interface():转换为interface{}类型

5.反射获取变量的值
    reflect.ValueOf(x).Float()     //获取浮点型的值
    reflect.ValueOf(x).Int()       // 获取int类型的值
    reflect.ValueOf(x).String()    // 获取字符串类型的值
    reflect.ValueOf(x).Bool()      // 获取布尔类型的值

6.反射设置变量的值(传值需要传地址,反射里需要用:参数.Elem()来获取地址)
    val := reflect.ValueOf(b)       // 获取参数的类型
    val.Elem().SetInt(20)        //修改int类型的值
    val.Elem().SetFloat(20.3)        //修改int类型的值
    val.Elem().SetString("set string")        //修改int类型的值

利用反射获取变量的值

type Student struct {     // 定义Student类型
    Name string
    Age int
    Score float32
    Sex string
}

// 利用反射获取变量的值
func test(b interface{}) {
    t := reflect.TypeOf(b)        // 获取b的类型
    fmt.Println(t)                // 结果为:main.Student

    v := reflect.ValueOf(b)       // 获取b的值(reflect.Value类型的值)
    fmt.Println(v.Kind())         // 获取v的类别,结果为:struct

    in := v.Interface()         // 将:reflect.Value类型的值转为interface类型的值
    stu, ok := in.(Student)     // 将interface类型的值转换为:Student类型的值
    if ok {
        fmt.Printf("%v,%T\n", stu, stu)      // 结果为:{zhangsan 18 88.3 男},main.Student
    }
}

// main函数
func main() {
    // 利用反射获取变量的值
    var a Student = Student {
        Name: "zhangsan",
        Age: 18,
        Score: 88.3,
        Sex: "男",
    }
    test(a)    // 执行test函数
}

利用反射修改变量的值

func testInt(b interface{}) {
    val := reflect.ValueOf(b)       // 获取b的类型
    val.Elem().SetInt(20)      //传进来的是地址(反射没有*val方法),反射提供了Elem()方法(效果和*val一样)。SetInt()设置值
    c := val.Elem().Int()      // 反射传进来的是地址时都要用到.Elem()方法
    fmt.Printf("set data: %d",c)     // 结果:set data: 20
}

func main() {
    var b int = 12
    testInt(&b)    // 要传地址(传值反射不接受,会报错)
}

反射结构体

// 定义Student的print方法
func (self Student) Print() {
    fmt.Println("in Struct Print:",self)
}

// 利用反射操作结构体
func testStruct(data Student) {
    val := reflect.ValueOf(data)       // 获取data的类型
    kd := val.Kind()      // 获取类别
    if kd != reflect.Struct {          // 判断类别不是struct,就抛出提示信息
        fmt.Println("expect struct")
        return
    }
    num := val.NumField()     // NumField() 获取结构体字段的数量
    // 打印结构体字段的信息
    for i :=0; i < num; i ++ {
        fmt.Printf("bumfield - %d, %v, %v\n",i, val.Field(i), val.Field(i).Kind())    // i是下标,val.Field(i)是下标对应字段的值,val.Field(i).Kind()是字段的类型
        /*
        结果:
            bumfield - 0, zhangsan, string
            bumfield - 1, 18, int
            bumfield - 2, 88.3, float32
            bumfield - 3, 男, string
        */
    }

    method := val.NumMethod()     // NumMethod()  获取结构体方法的数量
    fmt.Printf("struct has %d method\n",method)       // 结果:struct has 1 method

    val.Method(0).Call(nil)    // Method(0).Call(nil):调用struct下标为0的方法(即print方法,需要传一个空值)
}

func main() {
    // 利用反射获取变量的值
    var a Student = Student {    // 初始化结构体
        Name: "zhangsan",
        Age: 18,
        Score: 88.3,
        Sex: "男",
    }
    // 调用testStruct函数并将a结构体传给函数
    testStruct(a)

总结:

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。

  2. 大量使用反射的代码通常难以理解。

  3. 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: