go-反射

Posted ygjzs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go-反射相关的知识,希望对你有一定的参考价值。

反射

反射的基本介绍

17.3.1 基本介绍
1) 反射可以在运行时 动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
2) 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的 字段、 方法)
3) 通过反射,可以修改变量的值,可以调用关联的方法。
4) 使用反射,需要 import (“reflect”)

反射重要的函数和概念

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

2) reflect.Value(变量名),获取变量的值,返回reflect.Value(是一个结构体类型)
3) 变量、interface{} 和 reflect.Value 是可以相互转换的,这点在实际开发中,会经常使用到

反射的快速入门

快速入门说明

请编写一个案例,演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作

代码演示,见下面的表格:
请编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作

package main
import (
    "reflect"
    "fmt"
)


//专门演示反射
func reflectTest01(b interface{}) {

    //通过反射获取的传入的变量的 type , kind, 值
    //1. 先获取到 reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rType=", rTyp)

    //2. 获取到 reflect.Value
    rVal := reflect.ValueOf(b)
    
    n2 := 2 + rVal.Int()
    //n3 := rVal.Float()
    fmt.Println("n2=", n2)
    //fmt.Println("n3=", n3)
    
    fmt.Printf("rVal=%v rVal type=%T
", rVal, rVal)
    fmt.Printf("rVal=%v rTyp type=%T
", rVal, rTyp)

    //下面我们将 rVal 转成 interface{}
    iV := rVal.Interface()
    //将 interface{} 通过断言转成需要的类型
    num2 := iV.(int)
    fmt.Println("num2=", num2)


}

//专门演示反射[对结构体的反射]
func reflectTest02(b interface{}) {

    //通过反射获取的传入的变量的 type , kind, 值
    //1. 先获取到 reflect.Type
    rTyp := reflect.TypeOf(b)
    fmt.Println("rType=", rTyp)

    //2. 获取到 reflect.Value
    rVal := reflect.ValueOf(b)

    //3. 获取 变量对应的Kind
    //(1) rVal.Kind() ==> 
    kind1 := rVal.Kind()
    //(2) rTyp.Kind() ==>
    kind2 := rTyp.Kind()
    fmt.Printf("kind =%v kind=%v
", kind1, kind2)
    


    //下面我们将 rVal 转成 interface{}
    iV := rVal.Interface()
    fmt.Printf("iv=%v iv type=%T 
", iV, iV)
    //将 interface{} 通过断言转成需要的类型
    //这里,我们就简单使用了一带检测的类型断言.
    //同学们可以使用 swtich 的断言形式来做的更加的灵活
    stu, ok := iV.(Student)
    if ok {
        fmt.Printf("stu.Name=%v
", stu.Name)
    }

}

type Student struct {
    Name string
    Age int
}

type Monster struct {
    Name string
    Age int
}

func main() {

    //请编写一个案例,
    //演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作

    //1. 先定义一个int
    var num int = 100
    reflectTest01(num)

    //2. 定义一个Student的实例
    stu := Student{
        Name : "tom",
        Age : 20,
    }
    reflectTest02(stu)


}

反射的注意事项和细节

1) reflect.Value.Kind,获取变量的类别,返回的是一个常量
2) Type 和 Kind 的区别:

Type 是类型, Kind 是类别, Type 和 Kind 可能是相同的,也 可能是不同的.
比如: var num int = 10 num 的 Type 是 int , Kind 也是 int
比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct

3) 通过反射可以让变量在interface{}和reflect.Value之间互相转换。
4) 通过反射获取的变量的值,要求数据类型配,比如x是int,那么就应该用reflect.Value.int(),而不能用其他,否则会报panic的错误。
5) 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法


package main
import (
    "reflect"
    "fmt"
)

//通过反射,修改,
// num int 的值
// 修改 student的值

func reflect01(b interface{}) {
    //2. 获取到 reflect.Value
    rVal := reflect.ValueOf(b)
    // 看看 rVal的Kind是 
    fmt.Printf("rVal kind=%v
", rVal.Kind())
    //3. rVal
    //Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装
    rVal.Elem().SetInt(20)
}

func main() {

    var num int = 10
    reflect01(&num)
    fmt.Println("num=", num) // 20


    //你可以这样理解rVal.Elem()
    // num := 9
    // ptr *int = &num
    // num2 := *ptr  //=== 类似 rVal.Elem()
}
package main
import (
    "fmt"
    "reflect"
)
func main() {
    var str string = "tom"   //ok
    fs := reflect.ValueOf(&str) //ok fs -> string
    fs.Elem().SetString("jack") //ok
    fmt.Printf("%v
", str) // jack
}

反射最佳实践

1) 使用 反射来遍历结构体的字段, 调用结构体的方法,并 获取结构体标签的值
2) 使用反射的方式来获取结构体的 tag 标签, 遍历字段的值,修改字段值,调用结构体方法(要求:通过传递地址的方式完成, 在前面案例上修改即可)

3) 定义了两个函数 test1 和 test2,定义一个适配器函数用作统一处理接口(略,用反射实现即可)
4) 使用反射操作任意结构体类型:
5) 使用反射创建并操作结构体

package main
import (
    "fmt"
    "reflect"
)
//定义了一个Monster结构体
type Monster struct {
    Name  string `json:"name"`
    Age   int `json:"monster_age"`
    Score float32 `json:"成绩"`
    Sex   string
    
}

//方法,返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {
    return n1 + n2
}
//方法, 接收四个值,给s赋值
func (s Monster) Set(name string, age int, score float32, sex string) {
    s.Name = name
    s.Age = age
    s.Score = score
    s.Sex = sex
}

//方法,显示s的值
func (s Monster) Print() {
    fmt.Println("---start~----")
    fmt.Println(s)
    fmt.Println("---end~----")
}
func TestStruct(a interface{}) {
    //获取reflect.Type 类型
    typ := reflect.TypeOf(a)
    //获取reflect.Value 类型
    val := reflect.ValueOf(a)
    //获取到a对应的类别
    kd := val.Kind()
    //如果传入的不是struct,就退出
    if kd !=  reflect.Struct {
        fmt.Println("expect struct")
        return
    }

    //获取到该结构体有几个字段
    num := val.NumField()

    fmt.Printf("struct has %d fields
", num) //4
    //变量结构体的所有字段
    for i := 0; i < num; i++ {
        fmt.Printf("Field %d: 值为=%v
", i, val.Field(i))
        //获取到struct标签, 注意需要通过reflect.Type来获取tag标签的值
        tagVal := typ.Field(i).Tag.Get("json")
        //如果该字段于tag标签就显示,否则就不显示
        if tagVal != "" {
            fmt.Printf("Field %d: tag为=%v
", i, tagVal)
        }
    }
    
    //获取到该结构体有多少个方法
    numOfMethod := val.NumMethod()
    fmt.Printf("struct has %d methods
", numOfMethod)
    
    //var params []reflect.Value
    //方法的排序默认是按照 函数名的排序(ASCII码)
    val.Method(1).Call(nil) //获取到第二个方法。调用它

    
    //调用结构体的第1个方法Method(0)
    var params []reflect.Value  //声明了 []reflect.Value
    params = append(params, reflect.ValueOf(10))
    params = append(params, reflect.ValueOf(40))
    res := val.Method(0).Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Value
    fmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/

}
func main() {
    //创建了一个Monster实例
    var a Monster = Monster{
        Name:  "黄鼠狼精",
        Age:   400,
        Score: 30.8,
    }
    //将Monster实例传递给TestStruct函数
    TestStruct(a)   
}

const介绍

package main
import (
    
    "fmt"
)
func main() {

    var num int
    num = 9 //ok
    //常量声明的时候,必须赋值。
    const tax int = 0 
    //常量是不能修改
    //tax = 10
    fmt.Println(num, tax)
    //常量只能修饰bool、数值类型(int, float系列)、string 类型
    
    //fmt.Println(b)

    const (
        a = iota
        b 
        c
        d
    )


    fmt.Println(a, b, c, d)//0 1 2 3
}


以上是关于go-反射的主要内容,如果未能解决你的问题,请参考以下文章

Go 每日一库之 reflect

Gin利用Go的反射原理进行路由注册

实战演示 Go 反射的使用方法和应用场景

Go 每日一库之 reflect

[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础

Go 处理yaml类型的配置文件