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++里面的模板函数了。