Skip to content

Commit

Permalink
第十一章修改 (#839)
Browse files Browse the repository at this point in the history
Co-authored-by: Joe Chen <[email protected]>
  • Loading branch information
loftea and unknwon authored May 12, 2022
1 parent d397979 commit e394361
Show file tree
Hide file tree
Showing 14 changed files with 86 additions and 89 deletions.
2 changes: 1 addition & 1 deletion eBook/11.0.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 11.0 接口interface与反射reflection
# 11.0 接口 (interface)与反射 (reflection)

本章介绍 Go 语言中接口和反射的相关内容。

Expand Down
17 changes: 8 additions & 9 deletions eBook/11.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func main() {
cannot use sq1 (type *Square) as type Shaper in assignment:
*Square does not implement Shaper (missing Area method)

如果 `Shaper` 有另外一个方法 `Perimeter()`,但是`Square` 没有实现它,即使没有人在 `Square` 实例上调用这个方法,编译器也会给出上面同样的错误。
如果 `Shaper` 有另外一个方法 `Perimeter()`,但是 `Square` 没有实现它,即使没有人在 `Square` 实例上调用这个方法,编译器也会给出上面同样的错误。

扩展一下上面的例子,类型 `Rectangle` 也实现了 `Shaper` 接口。接着创建一个 `Shaper` 类型的数组,迭代它的每一个元素并在上面调用 `Area()` 方法,以此来展示多态行为:

Expand Down Expand Up @@ -150,7 +150,7 @@ func main() {
Shape details: &{5}
Area of this shape is: 25

在调用 `shapes[n].Area() ` 这个时,只知道 `shapes[n]` 是一个 `Shaper` 对象,最后它摇身一变成为了一个 `Square``Rectangle` 对象,并且表现出了相对应的行为。
在调用 `shapes[n].Area()` 这个时,只知道 `shapes[n]` 是一个 `Shaper` 对象,最后它摇身一变成为了一个 `Square``Rectangle` 对象,并且表现出了相对应的行为。

也许从现在开始你将看到通过接口如何产生 **更干净****更简单****更具有扩展性** 的代码。在 11.12.3 中将看到在开发中为类型添加新的接口是多么的容易。

Expand Down Expand Up @@ -236,21 +236,20 @@ type Reader interface {

有的时候,也会以一种稍微不同的方式来使用接口这个词:从某个类型的角度来看,它的接口指的是:它的所有导出方法,只不过没有显式地为这些导出方法额外定一个接口而已。

**练习 11.1** simple_interface.go:
**练习 11.1** [simple_interface.go](exercises\chapter_11\simple_interface.go)

定义一个接口 `Simpler`,它有一个 `Get()` 方法和一个 `Set()``Get()`返回一个整型值,`Set()` 有一个整型参数。创建一个结构体类型 `Simple` 实现这个接口。
定义一个接口 `Simpler`,它有一个 `Get()` 方法和一个 `Set()``Get()` 返回一个整型值,`Set()` 有一个整型参数。创建一个结构体类型 `Simple` 实现这个接口。

接着定一个函数,它有一个 `Simpler` 类型的参数,调用参数的 `Get()``Set()` 方法。在 `main` 函数里调用这个函数,看看它是否可以正确运行。

**练习 11.2** interfaces_poly2.go:
**练习 11.2** [interfaces_poly2.go](exercises\chapter_11\interfaces_poly2.go)

a) 扩展 interfaces_poly.go 中的例子,添加一个 `Circle` 类型
a) 扩展 [interfaces_poly.go](exercises\chapter_11\interfaces_poly.go) 中的例子,添加一个 `Circle` 类型

b) 使用一个抽象类型 `Shape`(没有字段) 实现同样的功能,它实现接口 `Shaper`,然后在其他类型里内嵌此类型。扩展 10.6.5 中的例子来说明覆写。
b) 使用一个抽象类型 `Shape`(没有字段) 实现同样的功能,它实现接口 `Shaper`,然后在其他类型里内嵌此类型。扩展 [10.6.5](10.6.md) 中的例子来说明覆写。

## 链接

- [目录](directory.md)
- 上一节:[接口Interfaces与反射reflection](11.0.md)
- 上一节:[接口 (Interfaces) 与反射 (reflection)](11.0.md)
- 下一节:[接口嵌套接口](11.2.md)

34 changes: 16 additions & 18 deletions eBook/11.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

## 11.10.1 方法和类型的反射

在 10.4 节我们看到可以通过反射来分析一个结构体。本节我们进一步探讨强大的反射功能。反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如它的大小、方法和 `动态`
的调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。
[10.4](10.4.md) 节我们看到可以通过反射来分析一个结构体。本节我们进一步探讨强大的反射功能。反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如:它的大小、它的方法以及它能“动态地”调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

变量的最基本信息就是类型和值:反射包的 `Type` 用来表示一个 Go 类型,反射包的 `Value` 为 Go 值提供了反射接口。

Expand All @@ -20,7 +19,7 @@ func ValueOf(i interface{}) Value

反射可以从接口值反射到对象,也可以从对象反射回接口值。

reflect.Type 和 reflect.Value 都有许多方法用于检查和操作它们。一个重要的例子是 Value 有一个 Type 方法返回 reflect.Value 的 Type。另一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:UintFloat64Slice 等等。同样 Value 有叫做 Int 和 Float 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)
`reflect.Type``reflect.Value` 都有许多方法用于检查和操作它们。一个重要的例子是 `Value` 有一个 `Type()` 方法返回 `reflect.Value``Type` 类型。另一个是 `Type``Value` 都有 `Kind()` 方法返回一个常量来表示类型:`Uint``Float64``Slice` 等等。同样 `Value` 有叫做 `Int()``Float()` 的方法可以获取存储在内部的值(跟 `int64``float64` 一样)

```go
const (
Expand Down Expand Up @@ -54,10 +53,9 @@ const (
)
```

对于 float64 类型的变量 x,如果 `v:=reflect.ValueOf(x)`,那么 `v.Kind()` 返回 `reflect.Float64` ,所以下面的表达式是 `true`
`v.Kind() == reflect.Float64`
对于 `float64` 类型的变量 `x`,如果 `v:=reflect.ValueOf(x)`,那么 `v.Kind()` 返回 `reflect.Float64` ,所以下面的表达式是 `true``v.Kind() == reflect.Float64`

Kind 总是返回底层类型:
`Kind()` 总是返回底层类型:

```go
type MyInt int
Expand All @@ -67,7 +65,7 @@ v := reflect.ValueOf(m)

方法 `v.Kind()` 返回 `reflect.Int`

变量 v`Interface()` 方法可以得到还原(接口)值,所以可以这样打印 v 的值:`fmt.Println(v.Interface())`
变量 `v``Interface()` 方法可以得到还原(接口)值,所以可以这样打印 `v` 的值:`fmt.Println(v.Interface())`


尝试运行下面的代码:
Expand Down Expand Up @@ -111,25 +109,25 @@ value is 3.40e+00
3.4
```

x 是一个 float64 类型的值,`reflect.ValueOf(x).Float()` 返回这个 float64 类型的实际值;同样的适用于 `Int(), Bool(), Complex(), String()`
`x` 是一个 `float64` 类型的值,`reflect.ValueOf(x).Float()` 返回这个 `float64` 类型的实际值;同样的适用于 `Int(), Bool(), Complex(), String()`

## 11.10.2 通过反射修改(设置)
## 11.10.2 通过反射修改(设置)

继续前面的例子(参阅 11.9 [reflect2.go](examples/chapter_11/reflect2.go)),假设我们要把 x 的值改为 3.1415Value 有一些方法可以完成这个任务,但是必须小心使用:`v.SetFloat(3.1415)`
继续前面的例子(参阅 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` 不是可设置的(这里并不是说值不可寻址)。是否可设置是 `Value` 的一个属性,并且不是所有的反射值都有这个属性:可以使用 `CanSet()` 方法测试是否可设置。

在例子中我们看到 `v.CanSet()` 返回 false: `settability of v: false`
在例子中我们看到 `v.CanSet()` 返回 `false``settability of v: false`

`v := reflect.ValueOf(x)` 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 `v = reflect.ValueOf(&x)`
`v := reflect.ValueOf(x)` 函数通过传递一个 `x` 拷贝创建了 `v`,那么 `v` 的改变并不能更改原始的 `x`。要想 `v` 的更改能作用到 `x`,那就必须传递 x 的地址 `v = reflect.ValueOf(&x)`

通过 Type() 我们看到 v 现在的类型是 `*float64` 并且仍然是不可设置的。
通过 `Type()` 我们看到 `v` 现在的类型是 `*float64` 并且仍然是不可设置的。

要想让其可设置我们需要使用 `Elem()` 函数,这间接的使用指针`v = v.Elem()`
要想让其可设置我们需要使用 `Elem()` 函数,这间接地使用指针`v = v.Elem()`

现在 `v.CanSet()` 返回 true 并且 `v.SetFloat(3.1415)` 设置成功了!
现在 `v.CanSet()` 返回 `true` 并且 `v.SetFloat(3.1415)` 设置成功了!


示例 11.12 [reflect2.go](examples/chapter_11/reflect2.go)
Expand Down Expand Up @@ -176,9 +174,9 @@ settability of v: true

## 11.10.3 反射结构

有些时候需要反射一个结构类型。`NumField()` 方法返回结构内的字段数量;通过一个 for 循环用索引取得每个字段的值 `Field(i)`
有些时候需要反射一个结构类型。`NumField()` 方法返回结构内的字段数量;通过一个 `for` 循环用索引取得每个字段的值 `Field(i)`

我们同样能够调用签名在结构上的方法,例如,使用索引 n 来调用:`Method(n).Call(nil)`
我们同样能够调用签名在结构上的方法,例如,使用索引 `n` 来调用:`Method(n).Call(nil)`

示例 11.13 [reflect_struct.go](examples/chapter_11/reflect_struct.go)

Expand Down
12 changes: 6 additions & 6 deletions eBook/11.11.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# 11.11 Printf 和反射
# 11.11 Printf() 和反射

在 Go 语言的标准库中,前几节所述的反射的功能被大量地使用。举个例子,fmt 包中的 Printf(以及其他格式化输出函数)都会使用反射来分析它的 `...` 参数。
在 Go 语言的标准库中,前几节所述的反射的功能被大量地使用。举个例子,`fmt` 包中的 `Printf()`(以及其他格式化输出函数)都会使用反射来分析它的 `...` 参数。

Printf 的函数声明为:
`Printf()` 的函数声明为:

```go
func Printf(format string, args ... interface{}) (n int, err error)
```

Printf 中的 `...` 参数为空接口类型。Printf 使用反射包来解析这个参数列表。所以,Printf 能够知道它每个参数的类型。因此格式化字符串中只有 %d 而没有 %u 和 %ld,因为它知道这个参数是 unsigned 还是 long。这也是为什么 Print 和 Println 在没有格式字符串的情况下还能如此漂亮地输出。
`Printf()` 中的 `...` 参数为空接口类型。`Printf()` 使用反射包来解析这个参数列表。所以,`Printf()` 能够知道它每个参数的类型。因此格式化字符串中只有 `%d` 而没有 `%u``%ld`,因为它知道这个参数是 unsigned 还是 long。这也是为什么 `Print()``Println()` 在没有格式字符串的情况下还能如此漂亮地输出。

为了让大家更加具体地了解 Printf 中的反射,我们实现了一个简单的通用输出函数。其中使用了 type-switch 来推导参数类型,并根据类型来输出每个参数的值(这里用了 10.7 节中练习 10.13 的部分代码)
为了让大家更加具体地了解 `Printf()` 中的反射,我们实现了一个简单的通用输出函数。其中使用了 type-switch 来推导参数类型,并根据类型来输出每个参数的值(这里用了 [10.7](10.7.md) 节中练习 10.13 的部分代码)

示例 11.15 [print.go](examples/chapter_11/print.go):

Expand Down Expand Up @@ -58,7 +58,7 @@ func main() {
}
```

在 12.8 节中我们将阐释 `fmt.Fprintf()` 是怎么运用同样的反射原则的。
[12.8](12.8.md) 节中我们将阐释 `fmt.Fprintf()` 是怎么运用同样的反射原则的。

## 链接

Expand Down
Loading

0 comments on commit e394361

Please sign in to comment.