Go语言中的反射

反射

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

方法和类型的反射

两个简单的函数: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语言中的反射)