zoukankan      html  css  js  c++  java
  • GO语言基础之reflect反射

    反射reflection

      1. 反射可以大大的提高程序的灵活性,使得 interface{} 有更大的发挥余地

      2. 反射使用 TypeOf 和 ValueOf 函数从接口中获取目标对象信息

      3. 反射会将匿名字段作为独立字段(匿名字段本质)

      4. 想要利用反射修改对象状态,前提是 interface.data 是 settable,即 pointer-interface

      5. 通过反射可以“动态”调用方法

    示例一:

      举例说明反射使用 TypeOf 和 ValueOf 来取得传入类型的属性字段于方法

    复制代码
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    //定义一个用户结构体
    type User struct {
        Id int
        Name string
        Age int
    }
    
    //为接口绑定方法
    func (u User) Hello() {
        fmt.Println("Hello World.")
    }
    
    //定义一个可接受任何类型的函数(空接口的使用规则)
    func Info(o interface{}) {
        t := reflect.TypeOf(o)    //获取接受到到接口到类型
        fmt.Println("Type:", t.Name())    //打印对应类型到名称(这是reflect中自带到)
    
        //Kind()方法是得到传入类型到返回类型;下面执行判断传入类型是否为一个结构体
        if k := t.Kind(); k != reflect.Struct {
            fmt.Println("传入的类型有误,请检查!")
            return
        }
    
        v := reflect.ValueOf(o)    //获取接受到到接口类型包含到内容(即其中到属性字段和方法)
        fmt.Println("Fields:")  //如何将其中到所有字段和内容打印出来呢?
        /**
        通过接口类型.NumField 获取当前类型所有字段个数
         */
        for i := 0; i < t.NumField(); i++ {
            f := t.Field(i)            //取得对应索引的字段
            val := v.Field(i).Interface()    //取得当前字段对应的内容
            fmt.Printf("%6s: %v = %v
    ", f.Name, f.Type, val)
        }
        /**
        通过接口类型.NumMethod 获取当前类型所有方法的个数
         */
        fmt.Println("Method:")
        for i := 0; i < t.NumMethod(); i++ {
            m := t.Method(i)        //取得对应索引的方法
            fmt.Printf("%6s: %v
    ", m.Name, m.Type)
        }
    }
    
    func main()  {
        u := User{1, "OK", 12}
        Info(u)
        //Info(&u) 如果传入的是结构体的地址或指针(pointer-interface),那么在Info函数中的Kind方法进行判断时就会被拦截返回
    }
    复制代码

    运行结果如下:

    1
    2
    3
    4
    5
    6
    7
    Type: User
    Fields:
        Id: int = 1
      Name: string = OK
       Age: int = 12
    Method:
     Hello: func(main.User)

    示例二:

      如何通过反射得道结构当中匿名或者嵌入字段

    复制代码
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    //定义一个用户结构体
    type User struct {
        Id int
        Name string
        Age int
    }
    
    type Manager struct {
        User    //定义了一个匿名引用
        title string
    }
    
    func main() {
        m := Manager{User: User{1, "OK", 15}, title: "123"}
        t := reflect.TypeOf(m)
    
        //取得类型中的字段是否为匿名字段
        fmt.Printf("%6v
    ", t.Field(0))
        /**
        打印内容:{User main.User  0 [ 0] true},其中true表示是匿名类型
        那么想要取匿名类型中的字段又该怎么取呢?这里需要使用序号组,传入要取的切片即可
         */
        fmt.Printf("%v
    ", t.FieldByIndex([]int{0, 0}))
        /**
        其中上面切片传入的是{0, 0},
        1. 第一个0表示当前结构Manager取匿名User是第一个即为0
        2. 第二个0表示取得的结构User中要取第一个元素Id相对于User来说也是第一个即为0,如果要取Name则需传入[]int{0, 1}
        那么既然可以取出来内容,那么我们就可以尝试着进行修改,怎么做呢?
         */
        tchage := reflect.ValueOf(&m)    //想要修改和我们之前所说的传入值类型和指针类型是一致的,要想修改需要传入对应指针类型
        tchage.Elem().FieldByIndex([]int{0, 0}).SetInt(999) //传入指针需要通过 .Elem() 来取得对应的值内容,之后再想取哪个再继续使用序号组
        fmt.Println(tchage.Elem().FieldByName("title"))
        fmt.Println(tchage)
    }
    复制代码

    运行结果:

    1
    2
    3
    4
    {  User        main.User             0 [     0]   true}
    {Id  int  0 [0] false}
    123
    &{{999 OK 15} 123}

    示例三:

      那么让我们来写一个比较完整的通过反射修改结构体内部字段内容

    复制代码
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    //定义一个用户结构体
    type User struct {
        Id int
        Name string
        Age int
    }
    
    func main() {
        u := User{1, "OK", 13}
        fmt.Println(u)
        Set(&u)
        fmt.Println(u)
    }
    
    //定义一个可以接受任何类型的空接口
    func Set(o interface{}) {
        v := reflect.ValueOf(o)
        //通过反射修改类型中的内容需要传入指针,为了防止传入有误故在这里进行相关过滤验证判断(这前这快是已经说过的)
        if v.Kind() == reflect.Ptr && !v.Elem().CanSet() {
            //reflect.Ptr对应为指针类型;v.Elem().CanSet()取得对应地址下的内容并查看其是否可以进行修改
            fmt.Println("传入的类型有误,请检查!")
            return
        } else {
            v = v.Elem()    //将实际对象(包含详情内容)进行赋值
        }
    
        f := v.FieldByName("Name")
        f1 := v.FieldByName("Id1")
        if !f.IsValid() {    //判断通过名称获取得到到内容是否为空值
            fmt.Println("没有Name对应属性字段")
            return
        }
        if !f1.IsValid() {
            fmt.Println("没有Id1对应属性字段")
        }
        if f.Kind() == reflect.String {
            f.SetString("HelloWorld")
        }
    }
    复制代码

    运行结果:

    1
    2
    3
    {1 OK 13}
    没有Id1对应属性字段
    {1 HelloWorld 13}

    示例四:

      那么让我们来写一个比较完整的通过反射对方法等动态调用

    复制代码
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    //定义一个用户结构体
    type User struct {
        Id int
        Name string
        Age int
    }
    
    //为User绑定方法
    func (u User) HelloDisplay(name string) {
        fmt.Println("Hello", name, " my name is ", u.Name)
    }
    
    func main() {
        u := User{1, "OK", 29}
        u.HelloDisplay("jack") //正常调用
    
        /**
        以下方式为反射调用,最优到代码写法就是新写一个方法且在开始是通过kind判断类型是否正确且需要判断有没有对应方法等
         */
        v := reflect.ValueOf(u)    //通过反射得到类型内容
        methodV := v.MethodByName("HelloDisplay")  //通过方法名称得道方法实体
        args := []reflect.Value{reflect.ValueOf("jack")}  //设置反射传入的参数
        methodV.Call(args)
    }
    复制代码

    运行结果:

    1
    2
    Hello jack  my name is  OK
    Hello jack  my name is  OK
  • 相关阅读:
    Luogu P4316 绿豆蛙的归宿 题解报告
    Luogu P1850 换教室(NOIP 2016) 题解报告
    Rainbow的信号 题解报告
    $CH5105 Cookies$ 线性$DP+$贪心
    算法竞赛 $0×50$ 动态规划 (+一本通
    $CH5104 I-country$ 线性$DP$
    洛谷$2014$ 选课 背包类树形$DP$
    $SP703 Mobile Service DP$
    $POJ1015 Jury Compromise Dp$/背包
    $POJ1742 Coins$ 多重背包+贪心
  • 原文地址:https://www.cnblogs.com/grimm/p/7575994.html
Copyright © 2011-2022 走看看