GO语言反射

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

enter image description here

以这段代码为例。

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

以上内容主要来自Go Blog的相关文章。

http://blog.golang.org/laws-of-reflection

http://research.swtch.com/interfaces

打个小广告:求一份实习。

共 2 个回复


hejxing

楼主在哪里呀?我们在深圳南山区,可以提供实习岗位,我对go语言也很感兴趣,欢迎来切磋!我的邮箱305135667&qq.com

# 0

ggaaooppeenngg

是和go有关的么?现在大二,能实习的时间比较短,只有寒暑假,我就是想在市场经济下学多点东西,没有钱都可以的。给你发了个小简历。

# 1