.Println("kind is uint8: ", v.Kind() == reflect.Uint8) // true.
x = uint8(v.Uint()) // v.Uint returns a uint64.
第二个特性:接口保存了数据项底层类型,而不是静态的类型,如果一个接口包含用户定义的整数类型的值,比如
type MyInt int
var x MyInt = 7
v := reflect.ValueOf(x)
则v的Kind
方法调用仍然返回的是reflect.Int
,尽管x的静态类型是MyInt。也可以说,Kind`不会像
Type`一样将MyInt和int当作两种类型来对待。
2.从映射对象到接口的值
像物理映射一样,Go中的映射也有其自身的相反性。
通过利用Interface
的方法我们可以将interface.Value
恢复至接口类型,实际上这个方法将type和value信息包装至interface类型并且返回该值。
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}
因此我们可以说
y := v.Interface().(float64) // y will have type float64.
fmt.Println(y)
打印float64类型的值,其实是接口类型变量v的映射。
或者我们可以这样做,fmt.Println
, fmt.Printf
等函数的参数尽管是空的接口类型也能运行,在fmt包里面解析出type和value的方法和我们上面的例子相似。因此所有正确打印reflect.Value
的方法都试通过interface的方法将值传递给格式化打印函数。
fmt.Println(v.Interface())
(为什么不是fmt.Println(v)
?因为通过v是reflect.Value类型.)因为我们的值底层是float64类型,因此我们甚至可以浮点类型的格式打印.
fmt.Printf("value is %7.1e\n", v.Interface())
结果是
3.4e+00
因此我们不用类型断言v.Interface{}到float64类型。因为接口类型内部保存着值的信息,Printf函数能够恢复这些信息。
简单的说Interface是ValueOf的反操作,除非这个值总是静态的Interface类型。
改变接口对象,他的值必须是可改变的
第三法则比较微妙并且容易混淆,但是如果从第一准则开始看的话,那么还是比较容易理解的。
这是一条错误的语句,但是这个错误值得我们研究
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
如果你运行这条语句则会有下面的报错信息
panic: reflect.Value.SetFloat using unaddressable value
因为变量v是不可更改的,所以提示值7.1是不可寻址的。可赋值是value的一个特性,但是并不是所以的value都具有这个特性。
CanSet
方法返回该值是否是可以改变的,比如
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
结果是
settability of v: false
如果在不可以赋值的变量上进行赋值,就回引起错误。但是到底是什么才是可以赋值的呢?
可赋值的有点像是可寻址的,但是会更严格。映射对象可以更改存储值的特性可以用来创建新的映射对象。映射对象包含原始的数据项是决定映射对象可赋值的关键。当下面代码运行时
var x float64 = 3.4
v := reflect.ValueOf(x)
只是将x的拷贝到reflect.ValueOf
,因此reflect.ValueOf
的返回值是x的复制项,而不是x本身。假如下面这条语句可以正常运行
v.SetFloat(5.4)
尽管v看起来是由x创建的,但是并不会更新x的值,因为这条语句会更新x拷贝值的值,但是并不影响x本身,因此可更改的这一特性就是为了避免这种操作。
虽然这看起来很古怪,但其实这是一种很熟悉的操作。比如我们将x值赋值给一个方法
f(x)
我们本身不想修改x的值,因为传入的只是x值的拷贝,但是如果我们想修改x的值,那么我们需要传送x的地址(也就是x的指针)
f(&x)
这种操作是简单明了的,其实对于映射也是一样的。如果我们想通过映射修改x的值,那么我们需要传送x的指针。比如
var x float64 = 3.4
p := reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())
结果
type of p: *float64
settability of p: false
映射对象p仍然是不可修改的,但是其实我们并不想修改p,而是*p。为了得到指针的指向,我们需要使用Elem()
方法,该方法将会指向*p的值,并且将其保存到映射变量中
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())
结果为
settability of v: true
现在v是一个可修改的映射对象。并且v代表x,因此我们可以使用v.SetFloat()
来修改x的值。
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)
输出结果为
7.1
7.1
映射是比较难理解的,尽管我们通过映射的Values``Types
隐藏了到底发生了什么操作。我们只需要记住如果想改变它的值,那在调用ValuesOf
方法时应该使用指向它的指针。
Struct
在上一个例子中v并不是指向自身的指针,而是通过其他方式产生的。还有一种常用的操作就是修改结构体的某个字段,只要我们知道了结构体的地址,我们就能修改它的字段。
这有一个修改结构体变量t的例子。因为我们要修改结构体的字段,所以我们使用结构体指针创建结构体对象。我们使用typeOfT代表t的数据类型,并通过NumField方法迭代结构体的字段。主意:我们只是提取出结构体类型字段的的名字,而他们的reflect.Value
对象。
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())
}
输出结果是
0: A