Files
the-way-to-go_ZH_CN/eBook/11.10.md
Haigang Zhou e394361869 第十一章修改 (#839)
Co-authored-by: Joe Chen <jc@unknwon.io>
2022-05-12 21:55:24 +08:00

288 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 11.10 反射包
## 11.10.1 方法和类型的反射
在 [10.4](10.4.md) 节我们看到可以通过反射来分析一个结构体。本节我们进一步探讨强大的反射功能。反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如:它的大小、它的方法以及它能“动态地”调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。
变量的最基本信息就是类型和值:反射包的 `Type` 用来表示一个 Go 类型,反射包的 `Value` 为 Go 值提供了反射接口。
两个简单的函数,`reflect.TypeOf``reflect.ValueOf`返回被检查对象的类型和值。例如x 被定义为:`var x float64 = 3.4`,那么 `reflect.TypeOf(x)` 返回 `float64``reflect.ValueOf(x)` 返回 `<float64 Value>`
实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:
```go
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
```
接口的值包含一个 type 和 value。
反射可以从接口值反射到对象,也可以从对象反射回接口值。
`reflect.Type``reflect.Value` 都有许多方法用于检查和操作它们。一个重要的例子是 `Value` 有一个 `Type()` 方法返回 `reflect.Value``Type` 类型。另一个是 `Type``Value` 都有 `Kind()` 方法返回一个常量来表示类型:`Uint``Float64``Slice` 等等。同样 `Value` 有叫做 `Int()``Float()` 的方法可以获取存储在内部的值(跟 `int64``float64` 一样)
```go
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Ptr
Slice
String
Struct
UnsafePointer
)
```
对于 `float64` 类型的变量 `x`,如果 `v:=reflect.ValueOf(x)`,那么 `v.Kind()` 返回 `reflect.Float64` ,所以下面的表达式是 `true``v.Kind() == reflect.Float64`
`Kind()` 总是返回底层类型:
```go
type MyInt int
var m MyInt = 5
v := reflect.ValueOf(m)
```
方法 `v.Kind()` 返回 `reflect.Int`
变量 `v``Interface()` 方法可以得到还原(接口)值,所以可以这样打印 `v` 的值:`fmt.Println(v.Interface())`
尝试运行下面的代码:
示例 11.11 [reflect1.go](examples/chapter_11/reflect1.go)
```go
// blog: Laws of Reflection
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)
}
```
输出:
```
type: float64
value: 3.4
type: float64
kind: float64
value: 3.4
3.4
value is 3.40e+00
3.4
```
`x` 是一个 `float64` 类型的值,`reflect.ValueOf(x).Float()` 返回这个 `float64` 类型的实际值;同样的适用于 `Int(), Bool(), Complex(), String()`
## 11.10.2 通过反射修改(设置)值
继续前面的例子(参阅 11.9 [reflect2.go](examples/chapter_11/reflect2.go)),假设我们要把 `x` 的值改为 `3.1415``Value` 有一些方法可以完成这个任务,但是必须小心使用:`v.SetFloat(3.1415)`
这将产生一个错误:`reflect.Value.SetFloat using unaddressable value`
为什么会这样呢?问题的原因是 `v` 不是可设置的(这里并不是说值不可寻址)。是否可设置是 `Value` 的一个属性,并且不是所有的反射值都有这个属性:可以使用 `CanSet()` 方法测试是否可设置。
在例子中我们看到 `v.CanSet()` 返回 `false` `settability of v: false`
`v := reflect.ValueOf(x)` 函数通过传递一个 `x` 拷贝创建了 `v`,那么 `v` 的改变并不能更改原始的 `x`。要想 `v` 的更改能作用到 `x`,那就必须传递 x 的地址 `v = reflect.ValueOf(&x)`
通过 `Type()` 我们看到 `v` 现在的类型是 `*float64` 并且仍然是不可设置的。
要想让其可设置我们需要使用 `Elem()` 函数,这间接地使用指针:`v = v.Elem()`
现在 `v.CanSet()` 返回 `true` 并且 `v.SetFloat(3.1415)` 设置成功了!
示例 11.12 [reflect2.go](examples/chapter_11/reflect2.go)
```go
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
// setting a value:
// v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
fmt.Println("settability of v:", v.CanSet())
v = reflect.ValueOf(&x) // Note: take the address of x.
fmt.Println("type of v:", v.Type())
fmt.Println("settability of v:", v.CanSet())
v = v.Elem()
fmt.Println("The Elem of v is: ", v)
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(3.1415) // this works!
fmt.Println(v.Interface())
fmt.Println(v)
}
```
输出:
```
settability of v: false
type of v: *float64
settability of v: false
The Elem of v is: <float64 Value>
settability of v: true
3.1415
<float64 Value>
```
反射中有些内容是需要用地址去改变它的状态的。
## 11.10.3 反射结构
有些时候需要反射一个结构类型。`NumField()` 方法返回结构内的字段数量;通过一个 `for` 循环用索引取得每个字段的值 `Field(i)`
我们同样能够调用签名在结构上的方法,例如,使用索引 `n` 来调用:`Method(n).Call(nil)`
示例 11.13 [reflect_struct.go](examples/chapter_11/reflect_struct.go)
```go
package main
import (
"fmt"
"reflect"
)
type NotknownType struct {
s1, s2, s3 string
}
func (n NotknownType) String() string {
return n.s1 + " - " + n.s2 + " - " + n.s3
}
// variable to investigate:
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
func main() {
value := reflect.ValueOf(secret) // <main.NotknownType Value>
typ := reflect.TypeOf(secret) // main.NotknownType
// alternative:
// typ := value.Type() // main.NotknownType
fmt.Println(typ)
knd := value.Kind() // struct
fmt.Println(knd)
// iterate through the fields of the struct:
for i := 0; i < value.NumField(); i++ {
fmt.Printf("Field %d: %v\n", i, value.Field(i))
// error: panic: reflect.Value.SetString using value obtained using unexported field
// value.Field(i).SetString("C#")
}
// call the first method, which is String():
results := value.Method(0).Call(nil)
fmt.Println(results) // [Ada - Go - Oberon]
}
```
输出:
```
main.NotknownType
struct
Field 0: Ada
Field 1: Go
Field 2: Oberon
[Ada - Go - Oberon]
```
但是如果尝试更改一个值,会得到一个错误:
```
panic: reflect.Value.SetString using value obtained using unexported field
```
这是因为结构中只有被导出字段(首字母大写)才是可设置的;来看下面的例子:
示例 11.14 [reflect_struct2.go](examples/chapter_11/reflect_struct2.go)
```go
package main
import (
"fmt"
"reflect"
)
type T struct {
A int
B string
}
func main() {
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())
}
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)
}
```
输出:
```
0: A int = 23
1: B string = skidoo
t is now {77 Sunset Strip}
```
附录 37 深入阐述了反射概念。
## 链接
- [目录](directory.md)
- 上一节:[空接口](11.9.md)
- 下一节:[Printf 和反射](11.11.md)