update ch13-02 close #695

This commit is contained in:
KaiserY 2023-01-30 18:25:58 +08:00
parent d982f6d871
commit 295b55f57c

View File

@ -2,27 +2,28 @@
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md) > [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md)
> <br> > <br>
> commit f207d4125b28f51a9bd962b3232cdea925660073 > commit eabaaaa90ee6937db3690dc56f739116be55ecb2
迭代器模式允许你对一个序列的项进行某些处理。**迭代器***iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。 迭代器模式允许你对一个序列的项进行某些处理。**迭代器***iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
在 Rust 中,迭代器是 **惰性的***lazy*),这意味着在调用方法使用迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处: 在 Rust 中,迭代器是 **惰性的***lazy*),这意味着在调用方法使用迭代器之前它都不会有效果。例如,示例 13-10 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
```rust ```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/main.rs:here}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}}
``` ```
<span class="caption">示例 13-13:创建一个迭代器</span> <span class="caption">示例 13-10:创建一个迭代器</span>
一旦创建迭代器之后,可以选择用多种方式利用它。在第三章的示例 3-5 中,我们使用迭代器和 `for` 循环在每一个项上执行了一些代码,虽然直到现在为止我们一直没有具体讨论调用 `iter` 到底具体做了什么 迭代器被储存在 `v1_iter` 变量中。一旦创建迭代器之后,可以选择用多种方式利用它。在第三章的示例 3-5 中,我们使用 `for` 循环来遍历一个数组并在每一个项上执行了一些代码。在底层它隐式地创建并接着消费了一个迭代器,不过直到现在我们都一笔带过了它具体是如何工作的
示例 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
示例 13-11 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
```rust ```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-14/src/main.rs:here}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}}
``` ```
<span class="caption">示例 13-14:在一个 `for` 循环中使用迭代器</span> <span class="caption">示例 13-11:在一个 `for` 循环中使用迭代器</span>
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。 在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。
@ -46,16 +47,15 @@ pub trait Iterator {
`next``Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None` `next``Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None`
可以直接调用迭代器的 `next` 方法;示例 13-15 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值: 可以直接调用迭代器的 `next` 方法;示例 13-12 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值:
<span class="filename">文件名src/lib.rs</span> <span class="filename">文件名src/lib.rs</span>
```rust,noplayground ```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/lib.rs:here}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-12/src/lib.rs:here}}
``` ```
<span class="caption">示例 13-12在迭代器上直接调用 `next` 方法</span>
<span class="caption">示例 13-15在迭代器上直接调用 `next` 方法</span>
注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**consume或使用了迭代器。每一个 `next` 调用都会从迭代器中消费一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。 注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**consume或使用了迭代器。每一个 `next` 调用都会从迭代器中消费一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。
@ -65,15 +65,15 @@ pub trait Iterator {
`Iterator` trait 有一系列不同的由标准库提供默认实现的方法;你可以在 `Iterator` trait 的标准库 API 文档中找到所有这些方法。一些方法在其定义中调用了 `next` 方法,这也就是为什么在实现 `Iterator` trait 时要求实现 `next` 方法的原因。 `Iterator` trait 有一系列不同的由标准库提供默认实现的方法;你可以在 `Iterator` trait 的标准库 API 文档中找到所有这些方法。一些方法在其定义中调用了 `next` 方法,这也就是为什么在实现 `Iterator` trait 时要求实现 `next` 方法的原因。
这些调用 `next` 方法的方法被称为 **消费适配器***consuming adaptors*),因为调用他们会消耗迭代器。一个消费适配器的例子是 `sum` 方法。这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和。示例 13-16 有一个展示 `sum` 方法使用的测试: 这些调用 `next` 方法的方法被称为 **消费适配器***consuming adaptors*),因为调用他们会消耗迭代器。一个消费适配器的例子是 `sum` 方法。这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和。示例 13-13 有一个展示 `sum` 方法使用的测试:
<span class="filename">文件名src/lib.rs</span> <span class="filename">文件名src/lib.rs</span>
```rust,noplayground ```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs:here}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/lib.rs:here}}
``` ```
<span class="caption">示例 13-16:调用 `sum` 方法获取迭代器所有项的总和</span> <span class="caption">示例 13-13:调用 `sum` 方法获取迭代器所有项的总和</span>
调用 `sum` 之后不再允许使用 `v1_iter` 因为调用 `sum` 时它会获取迭代器的所有权。 调用 `sum` 之后不再允许使用 `v1_iter` 因为调用 `sum` 时它会获取迭代器的所有权。
@ -81,51 +81,55 @@ pub trait Iterator {
`Iterator` trait 中定义了另一类方法,被称为 **迭代器适配器***iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。 `Iterator` trait 中定义了另一类方法,被称为 **迭代器适配器***iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
示例 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,该 `map` 方法使用闭包来调用每个元素以生成新的迭代器。这里的闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1。不过这些代码会产生一个警告 示例 13-14 展示了一个调用迭代器适配器方法 `map` 的例子,该 `map` 方法使用闭包来调用每个元素以生成新的迭代器。这里的闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1。
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
```rust,not_desired_behavior ```rust,not_desired_behavior
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-17/src/main.rs:here}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-14/src/main.rs:here}}
``` ```
<span class="caption">示例 13-17:调用迭代器适配器 `map` 来创建一个新迭代器</span> <span class="caption">示例 13-14:调用迭代器适配器 `map` 来创建一个新迭代器</span>
得到的警告是: 不过这些代码会产生一个警告
```console ```console
{{#include ../listings/ch13-functional-features/listing-13-17/output.txt}} {{#include ../listings/ch13-functional-features/listing-13-14/output.txt}}
``` ```
示例 13-17 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们需要消费迭代器。 示例 13-14 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们需要消费迭代器。
为了修复这个警告并消费迭代器获取有用的结果,我们将使用第十二章示例 12-1 结合 `env::args` 使用的 `collect` 方法。这个方法消费迭代器并将结果收集到一个数据结构中。 为了修复这个警告并消费迭代器获取有用的结果,我们将使用第十二章示例 12-1 结合 `env::args` 使用的 `collect` 方法。这个方法消费迭代器并将结果收集到一个数据结构中。
在示例 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加 1 的结果: 在示例 13-15 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加 1 的结果:
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
```rust ```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-18/src/main.rs:here}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/main.rs:here}}
``` ```
<span class="caption">示例 13-18:调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector</span> <span class="caption">示例 13-15:调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector</span>
因为 `map` 获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。这是一个展示如何使用闭包来自定义行为同时又复用 `Iterator` trait 提供的迭代行为的绝佳例子。 因为 `map` 获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。这是一个展示如何使用闭包来自定义行为同时又复用 `Iterator` trait 提供的迭代行为的绝佳例子。
### 使用闭包获取环境 可以链式调用多个迭代器适配器来以一种可读的方式进行复杂的操作。不过因为所有的迭代器都是惰性的,你需要调用一个消费适配器方法从迭代器适配器调用中获取结果。
现在我们介绍了迭代器,让我们展示一个通过使用 `filter` 迭代器适配器和捕获环境的闭包的常规用例。迭代器的 `filter` 方法获取一个使用迭代器的每一个项并返回布尔值的闭包。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会包含在结果迭代器中。 ### 使用捕获其环境的闭包
示例 13-19 展示了使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包,这样闭包就可以遍历一个 `Shoe` 结构体集合以便只返回指定大小的鞋子: 很多迭代器适配器接受闭包作为参数,而通常指定为迭代器适配器参数的闭包会是捕获其环境的闭包。
作为一个例子,我们使用 `filter` 方法来获取一个闭包。该闭包从迭代器中获取一项并返回一个 `bool`。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会被包含。
示例 13-16 中使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包来遍历一个 `Shoe` 结构体集合。它只会返回指定大小的鞋子。
<span class="filename">文件名src/lib.rs</span> <span class="filename">文件名src/lib.rs</span>
```rust,noplayground ```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-19/src/lib.rs}} {{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs}}
``` ```
<span class="caption">示例 13-19:使用 `filter` 方法和一个捕获 `shoe_size` 的闭包</span> <span class="caption">示例 13-16:使用 `filter` 方法和一个捕获 `shoe_size` 的闭包</span>
`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。 `shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。
@ -134,67 +138,3 @@ pub trait Iterator {
闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。 闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
这个测试展示当调用 `shoes_in_my_size` 时,我们只会得到与指定值相同大小的鞋子。 这个测试展示当调用 `shoes_in_my_size` 时,我们只会得到与指定值相同大小的鞋子。
### 实现 `Iterator` trait 来创建自定义迭代器
我们已经展示了可以通过在 vector 上调用 `iter`、`into_iter` 或 `iter_mut` 来创建一个迭代器。也可以用标准库中其他的集合类型创建迭代器,比如哈希 map。另外可以实现 `Iterator` trait 来创建任何我们希望的迭代器。正如之前提到的,定义中唯一要求提供的方法就是 `next` 方法。一旦定义了它,就可以使用所有其他由 `Iterator` trait 提供的拥有默认实现的方法来创建自定义迭代器了!
作为展示,让我们创建一个只会从 1 数到 5 的迭代器。首先,创建一个结构体来存放一些值,接着实现 `Iterator` trait 将这个结构体放入迭代器中并在此实现中使用其值。
示例 13-20 有一个 `Counter` 结构体定义和一个创建 `Counter` 实例的关联函数 `new`
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-20/src/lib.rs}}
```
<span class="caption">示例 13-20定义 `Counter` 结构体和一个创建 `count` 初值为 0 的 `Counter` 实例的 `new` 函数</span>
`Counter` 结构体有一个字段 `count`。这个字段存放一个 `u32` 值,它会记录处理 1 到 5 的迭代过程中的位置。`count` 是私有的因为我们希望 `Counter` 的实现来管理这个值。`new` 函数通过总是从为 0 的 `count` 字段开始新实例来确保我们需要的行为。
接下来将为 `Counter` 类型实现 `Iterator` trait通过定义 `next` 方法来指定使用迭代器时的行为,如示例 13-21 所示:
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-21/src/lib.rs:here}}
```
<span class="caption">示例 13-21`Counter` 结构体上实现 `Iterator` trait</span>
这里将迭代器的关联类型 `Item` 设置为 `u32`,意味着迭代器会返回 `u32` 值集合。再一次,这里仍无需担心关联类型,第十九章会讲到。
我们希望迭代器对其内部状态加一,这也就是为何将 `count` 初始化为 0我们希望迭代器首先返回 1。如果 `count` 值小于 6`next` 会返回封装在 `Some` 中的当前值,不过如果 `count` 大于或等于 6迭代器会返回 `None`
#### 使用 `Counter` 迭代器的 `next` 方法
一旦实现了 `Iterator` trait我们就有了一个迭代器示例 13-22 展示了一个测试用来演示使用 `Counter` 结构体的迭代器功能,通过直接调用 `next` 方法,正如示例 13-15 中从 vector 创建的迭代器那样:
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-22/src/lib.rs:here}}
```
<span class="caption">示例 13-22测试 `next` 方法实现的功能</span>
这个测试在 `counter` 变量中新建了一个 `Counter` 实例并接着反复调用 `next` 方法,来验证我们实现的行为符合这个迭代器返回从 1 到 5 的值的预期。
#### 使用自定义迭代器中其他 `Iterator` trait 方法
通过定义 `next` 方法实现 `Iterator` trait我们现在就可以使用任何标准库定义的拥有默认实现的 `Iterator` trait 方法了,因为他们都使用了 `next` 方法的功能。
例如,出于某种原因我们希望获取 `Counter` 实例产生的值,将这些值与另一个 `Counter` 实例在省略了第一个值之后产生的值配对,将每一对值相乘,只保留那些可以被三整除的结果,然后将所有保留的结果相加,这可以如示例 13-23 中的测试这样做:
<span class="filename">文件名src/lib.rs</span>
```rust,noplayground
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-23/src/lib.rs:here}}
```
<span class="caption">示例 13-23使用自定义的 `Counter` 迭代器的多种方法</span>
注意 `zip` 只产生四对值;理论上第五对值 `(5, None)` 从未被产生,因为 `zip` 在任一输入迭代器返回 `None` 时也返回 `None`
所有这些方法调用都是可能的,因为我们指定了 `next` 方法如何工作,而标准库则提供了其它调用 `next` 的方法的默认实现。