Go 反射

 static, checked at compile time, dynamic when asked for 

 每个语言都有反射,我们的GO反射也很拽,不服来辩。

 Go语言是静态类型,所有数据的类型在编译器都是明确的,规定好了的。

 总之在编译的时候就决定了数据的类型的。

type MyInt int

var i int
var j MyInt

 即使是底层存的是一个类型,声明的类型不一样,也要强制转换才能互用。

另:go里面没有隐式转换,碰到类型不同只能强转。

牛逼哄哄地不让你有半点越界。

比如io package 里面的Reader和Writer。

只要实现了Reader的read方法,就可以按照read来存

var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

标准输入可以作reader,用bufio.NewReader给reader加一层缓冲的reader也可以作为reader,

bytes包里的Buffer也实现了read,所以也可以作为reader。

这样的话,一种类型能够存储的东西就多了。但是对外的调用接口是不变的。

静态类型的意义还是没有变,无论r存的是什么,它都是io.Reader。

好了,好戏上演。

最最极端的东西就是…………

interface{}

画外音:我就是一个什么接口也不要实现的接口类型,是不是显得很拉风?

之前这些,都是为了解释反射做的铺垫。有人说Go是动态类型的,那就只有“呵了个呵了个呵了个呵”啦。虽说存储的东西可能不一样,但是存储的东西始终都是满足了定义好的interface的。

 

 

type Stringer interface {
    String() string
}

type Binary uint64

func (i Binary) String() string {
    return strconv.Uitob64(i.Get(), 2)
}

func (i Binary) Get() uint64 {
    return uint64(i)
}

以这段代码为例。

Interface就存两个指针,一个是指向Binary的,另一个指向的是iTable(interface Table),

这个table,开始存数据的一些类型信息,后面接着存方法,比如fun[0]指向的就是binary的String。

binary的Get在这里面就没有了。

数据域的指针指向的是一个全新的拷贝,不是直接把指针指向了binary,就像var c uint64 = b一样的复制。

如果想要获得数据的type的话,就像C表达一样 s.tab->type 就可以了。

想要调用s.String()的方法的话,就直接s.tab->fun[0](),也就是(*Binary).String()

这里我们只考虑了只有一个method的interface,如果interface有很多的method话,在itab下面就会有更多的函数指针。

 

总之一句话,interface会把存的具体的数据存在data域里,类型和方法存在itab里面。

如果没有规定method的话,那么实际存储的时候只会保留type。

还是铺垫…… 空的interface存起来,就是这么回事。

 

接下来是refection

反射只是用来检测(type,value)pair的。也就是之前提到的数据域和itab域里的type。

相对应的,在refect的包里面,有Type和Value两个类型,而且Type和Value都提供了丰富的获取,改变,检查的函数,比如检查Func的参数,检查map的key,设置函数体等等。这两个类型提供接触interface值的

方法,refect.TypeOf 和refect.ValueOf 可以从interface取到对应的内容(从Value类型里面也可以轻松取到Type,但是为了解释清楚,先分开来说)。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x))
}

先举个栗子。

之前说reflect是用来取interface的内容的,你这是怎么回事?

实际上这个函数的定义是这样的。

// TypeOf returns the reflection Type of the value in the interface{}.
func TypeOf(i interface{}) Type

首先x会存储在空的interface里面,也就是根据之前那张图来存储。

空的interface可以存任何类型的数据,因为它不需要实现任何方法。

var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))

运行这段代码你就知道区别了,x的类型和数据都被存储了起来。

好啦。通过value和type这两个对象的一系列函数,我就能操作interface类型了。

 

valueOf 获得的是复制,对于它的修改对原来的数据是没有影响的。如果想要修改原值,就要用指针。再用p.Elem就能获得可以修改的对象了。

要处理struct的话,

也要像这样

type T struct {
    A int
    B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
}

通过Elem的获得可修改对象,然后再Filed获取对应的value。

这是一般类型。如果要处理channel,slice,func怎么办?

剩下的就是一大堆标准库函数去慢慢折腾了。

从官方的Func的example看看。

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // swap is the implementation passed to MakeFunc.
    // It must work in terms of reflect.Values so that it is possible
    // to write code without knowing beforehand what the types
    // will be.
    swap := func(in []reflect.Value) []reflect.Value {
        return []reflect.Value{in[1], in[0]}
    }

    // makeSwap expects fptr to be a pointer to a nil function.
    // It sets that pointer to a new function created with MakeFunc.
    // When the function is invoked, reflect turns the arguments
    // into Values, calls swap, and then turns swap's result slice
    // into the values returned by the new function.
    makeSwap := func(fptr interface{}) {
        // fptr is a pointer to a function.
        // Obtain the function value itself (likely nil) as a reflect.Value
        // so that we can query its type and then set the value.
        fn := reflect.ValueOf(fptr).Elem()

        // Make a function of the right type.
        v := reflect.MakeFunc(fn.Type(), swap)

        // Assign it to the value fn represents.
        fn.Set(v)
    }

    // Make and call a swap function for ints.
    var intSwap func(int, int) (int, int)
    makeSwap(&intSwap)
    fmt.Println(intSwap(0, 1))

    // Make and call a swap function for float64s.
    var floatSwap func(float64, float64) (float64, float64)
    makeSwap(&floatSwap)
    fmt.Println(floatSwap(2.72, 3.14))

}

这里也跟之前的那个x一样 intSwap和floatSwap都会存成interface{}

然后通过MakeFunc这个函数依据类型和swap来构造一个函数。

并且通过Set方法设置。

用之前的思路解释一遍,就是传入函数指针,这个指针会被存为interface。Value存的是空值,Type存的是 func(int,int)(int,int)或者func(float,float)(float,float)。

这个时候通过make构造一个Value出来给接口赋值,注意这里的区别,interface还是没有方法的空接口。

我们之前说的接口的方法是存在itab下面的,这里的Value是存在data域下面的。

因为这个对象是一个函数,所以函数的data域就是一个函数。

之后再调用intSwap和floatSwap都可以跑出相应的结果,就是通过这样的方式实体化了函数的内容。

这样就有点像C++里面的模板函数了。

 

posted @ 2014-03-21 15:19  ggaaooppeenngg  阅读(373)  评论(0编辑  收藏  举报