Go语言中的反射

作者: 帅气的昵称都有人用了 | 来源:发表于2019-04-02 21:13 被阅读1次

    反射

    反射是用程序检查代码中所拥有的结构尤其是类型的一种能力,这是元编程的一种形式。反射可以在运行时检查类型和变量。但是存在着一定的隐患,除非真的有必要,否则应当避免使用或者小心使用。

    方法和类型的反射

    两个简单的函数:reflect.TypeOfreflect.ValueOf,返回被检查对象的类型和值。
    两个函数的签名:

    func TypeOf(i interface{}) Type
    func ValueOf(i interface{}) Value
    

    其中,Type和Value都有Kind方法返回一个常量来表示类型:Uint,Float64,Slice等,同样Value有叫作Int,Float的方法可以获取存储在内部的值。

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var x float64 = 3.4
        fmt.Println("type:", reflect.TypeOf(x))
        v := reflect.ValueOf(x)
        fmt.Println("value: ", v)
        fmt.Println("type: ", v.Type())
        fmt.Println("kind: ", v.Kind())
        fmt.Println("value: ", v.Float())
        fmt.Println(v.Interface())
        fmt.Printf("value is %5.2e\n", v.Interface())
        y := v.Interface().(float64)
        fmt.Println(y)
    }
    
    /** The result is:
    type: float64
    value:  3.4
    type:  float64
    kind:  float64
    value:  3.4
    3.4
    value is 3.40e+00
    3.4
     */
    

    从上述代码中可以很好的看出每一个函数的功能。

    通过反射修改设置值

    Value中是有方法可以对x的值进行修改的,但是一定要谨慎使用v.SetFloat(3.1415)函数,因为可能会产生错误:

    /** reflect.Value.SetFloat using unaddressable value */
    

    其实我们是可以通过v.CanSet()来进行测试的,测试是否可以设置。如果想要解决上述问题,我们需要间接使用v=v.Elem()才能实现我们想要的效果

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        var x float64 = 3.4
        v := reflect.ValueOf(x)
        fmt.Println("v: ", v.CanSet())
        v = reflect.ValueOf(&x)
        fmt.Println("v的类型: ", v.Type())
        fmt.Println("v: ", v.CanSet())
        v = v.Elem()
        fmt.Println("v的Elem是: ", v)
        fmt.Println("v: ", v.CanSet())
        v.SetFloat(3.1415)
        fmt.Println(v.Interface())
        fmt.Println(v)
    }
    
    /** The result is:
    v:  false
    v的类型:  *float64
    v:  false
    v的Elem是:  3.4
    v:  true
    3.1415
    3.1415
     */
    

    反射结构

    有些时候需要反射一个结构类型:NumField()方法返回结构内的字段数量,通过一个for循环索引取得每个字段的值Field(i)

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type NotknownType struct {
        s1, s2, s3 string
    }
    
    func (n NotknownType) String() string {
        return n.s1 + "-" + n.s2 + "-" + n.s3
    }
    
    var secret interface{} = NotknownType{"Google", "Go", "Code"}
    
    func main() {
        value := reflect.ValueOf(secret)
        typ := reflect.TypeOf(secret)
        fmt.Println(typ)
        knd := value.Kind()
        fmt.Println(knd)
    
        for i := 0; i < value.NumField(); i++ {
            // 返回结构体的第i个字段(的Value封装)。如果v的Kind不是Struct或i出界会panic
            fmt.Printf("Field %d: %v\n", i, value.Field(i))
        }
    
    
        // 返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装。
        // 返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)。
        // 如果i出界,或者v的持有值是接口类型的零值(nil),会panic。
        results := value.Method(0).Call(nil)
        fmt.Println(results)
    }
    
    /** The result is:
    main.NotknownType
    struct
    Field 0: Google
    Field 1: Go
    Field 2: Code
    [Google-Go-Code]
     */
    

    同样,在尝试修改一个值的时候,会产生错误:

    panic:reflect.Value.SetString using value obtained using unexported field
    

    这是因为结构中只有被导出字段(首字母大写)才是可设置的

    Printf和反射

    fmt包中的Printf函数是使用反射机制来分析它的...参数的,Printf函数声明:

    func Printf(format string, args ...interface{}) (n int, err error)
    

    下面实现一个简单的通用输出函数,其中使用了type-switch来推导参数类型,并根据类型来输出每个参数的值:

    package main
    
    import (
        "os"
        "strconv"
    )
    
    type Stringer interface {
        String() string
    }
    
    type Celsius float64
    
    func (c Celsius) String() string {
        return strconv.FormatFloat(float64(c), 'f', 1, 64) + "C"
        /**
            func FormatFloat(f float64, fmt byte, prec, bitSize int) string
            函数将浮点数表示为字符串并返回。
            bitSize表示f的来源类型(32:float32、64:float64),会据此进行舍入。
            fmt表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)。
            prec控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f。
         */
    }
    
    type Day int
    
    var dayName = []string{"Monday", "Tudesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
    
    func (day Day) String() string {
        return dayName[day]
    }
    
    func printf(args ...interface{}) {
        for i, arg := range args {
            if i > 0 {os.Stdout.WriteString(" ")}
            switch a := arg.(type) { // 类型转换
            case Stringer: os.Stdout.WriteString(a.String())
            case int:      os.Stdout.WriteString(strconv.Itoa(a))
            case string:   os.Stdout.WriteString(a)
            // more type
            default:       os.Stdout.WriteString("???")
            }
        }
    }
    
    func main() {
        print(Day(1), "温度是", Celsius(18.36))
    }
    

    相关文章

      网友评论

        本文标题:Go语言中的反射

        本文链接:https://www.haomeiwen.com/subject/cuunbqtx.html