zoukankan      html  css  js  c++  java
  • GO_09: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方法进行判断时就会被拦截返回
    }

    运行结果如下:

    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)
    }

    运行结果:

    {  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 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)
    }

    运行结果:

    Hello jack  my name is  OK
    Hello jack  my name is  OK
    
  • 相关阅读:
    LeetCode 40. 组合总和 II(Combination Sum II)
    LeetCode 129. 求根到叶子节点数字之和(Sum Root to Leaf Numbers)
    LeetCode 60. 第k个排列(Permutation Sequence)
    LeetCode 47. 全排列 II(Permutations II)
    LeetCode 46. 全排列(Permutations)
    LeetCode 93. 复原IP地址(Restore IP Addresses)
    LeetCode 98. 验证二叉搜索树(Validate Binary Search Tree)
    LeetCode 59. 螺旋矩阵 II(Spiral Matrix II)
    一重指针和二重指针
    指针的意义
  • 原文地址:https://www.cnblogs.com/liang1101/p/6619100.html
Copyright © 2011-2022 走看看