|
|
|
|
## 使用迭代器处理元素序列
|
|
|
|
|
|
|
|
|
|
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md)
|
|
|
|
|
> <br>
|
|
|
|
|
> commit eabaaaa90ee6937db3690dc56f739116be55ecb2
|
|
|
|
|
|
|
|
|
|
迭代器模式允许你依次对一个序列中的项执行某些操作。**迭代器**(*iterator*)负责遍历序列中的每一项并确定序列何时结束的逻辑。使用迭代器时,你无需自己重新实现这些逻辑。
|
|
|
|
|
|
|
|
|
|
在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着在调用消费迭代器的方法之前不会执行任何操作。例如,示例 13-10 中的代码通过调用定义于 `Vec<T>` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身并没有执行任何有用的操作。
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-10:创建一个迭代器</span>
|
|
|
|
|
|
|
|
|
|
迭代器被储存在 `v1_iter` 变量中。一旦创建迭代器之后,可以选择用多种方式利用它。在第三章的示例 3-5 中,我们使用 `for` 循环来遍历一个数组并在每一个项上执行了一些代码。在底层它隐式地创建并接着消费了一个迭代器,不过直到现在我们都一笔带过了它具体是如何工作的。
|
|
|
|
|
|
|
|
|
|
示例 13-11 中的例子将迭代器的创建和 `for` 循环中的使用分开。当 `for` 循环使用 `v1_iter` 中的迭代器时,迭代器中的每一个元素都会用于循环的一次迭代,并打印出每个值。
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-11/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-11:在一个 `for` 循环中使用迭代器</span>
|
|
|
|
|
|
|
|
|
|
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 中的元素总量。
|
|
|
|
|
|
|
|
|
|
迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构。让我们看看迭代器是如何做到这些的。
|
|
|
|
|
|
|
|
|
|
### `Iterator` trait 和 `next` 方法
|
|
|
|
|
|
|
|
|
|
迭代器都实现了一个叫做 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像这样:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
pub trait Iterator {
|
|
|
|
|
type Item;
|
|
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item>;
|
|
|
|
|
|
|
|
|
|
// 此处省略了方法的默认实现
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
注意这里有一个我们还未讲到的新语法:`type Item` 和 `Self::Item`,它们定义了 trait 的 **关联类型**(*associated type*)。第二十章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。
|
|
|
|
|
|
|
|
|
|
`next` 是 `Iterator` 实现者被要求定义的唯一方法:`next` 方法,该方法每次返回迭代器中的一个项,封装在 `Some` 中,并且当迭代完成时,返回 `None`。
|
|
|
|
|
|
|
|
|
|
可以直接调用迭代器的 `next` 方法;示例 13-12 展示了对由 vector 创建的迭代器重复调用 `next` 方法时返回的值。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/lib.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust,noplayground
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-12/src/lib.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-12:在迭代器上(直接)调用 `next` 方法</span>
|
|
|
|
|
|
|
|
|
|
注意我们需要将 `v1_iter` 声明为可变的:在迭代器上调用 `next` 方法会改变迭代器内部的状态,该状态用于跟踪迭代器在序列中的位置。换句话说,代码 **消费**(consume)了,或者说用尽了迭代器。每一次 `next` 调用都会从迭代器中消费一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。
|
|
|
|
|
|
|
|
|
|
还需要注意的是,从 `next` 调用中获取的值是对 vector 中值的不可变引用。`iter` 方法生成一个不可变引用的迭代器。如果我们需要一个获取 `v1` 所有权并返回拥有所有权的迭代器,则可以调用 `into_iter` 而不是 `iter`。类似地,如果我们希望迭代可变引用,可以调用 `iter_mut` 而不是 `iter`。
|
|
|
|
|
|
|
|
|
|
### 消费迭代器的方法
|
|
|
|
|
|
|
|
|
|
`Iterator` trait 有一系列不同的由标准库提供默认实现的方法;你可以在 `Iterator` trait 的标准库 API 文档中找到所有这些方法。一些方法在其定义中调用了 `next` 方法,这也就是为什么在实现 `Iterator` trait 时要求实现 `next` 方法的原因。
|
|
|
|
|
|
|
|
|
|
这些调用 `next` 方法的方法被称为 **消费适配器**(*consuming adaptors*),因为调用它们会消耗迭代器。一个消费适配器的例子是 `sum` 方法。这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,因而会消费迭代器。在遍历过程中,它将每个项累加到一个总和中,并在迭代完成时返回这个总和。示例 13-13 有一个展示 `sum` 方法使用的测试:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/lib.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust,noplayground
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-13/src/lib.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-13:调用 `sum` 方法获取迭代器所有项的总和</span>
|
|
|
|
|
|
|
|
|
|
调用 `sum` 之后不再允许使用 `v1_iter` 因为调用 `sum` 时它会获取迭代器的所有权。
|
|
|
|
|
|
|
|
|
|
### 产生其他迭代器的方法
|
|
|
|
|
|
|
|
|
|
`Iterator` trait 中定义了另一类方法,被称为 **迭代器适配器**(*iterator adaptors*),它们不会消耗当前的迭代器,而是通过改变原始迭代器的某些方面来生成不同的迭代器。
|
|
|
|
|
|
|
|
|
|
示例 13-14 展示了一个调用迭代器适配器方法 `map` 的例子,该方法使用一个闭包对每个元素进行操作。`map` 方法返回一个新的迭代器,该迭代器生成经过修改的元素。这里的闭包创建了一个新的迭代器,其中 vector 中的每个元素都被加 1。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust,not_desired_behavior
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-14/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-14:调用迭代器适配器 `map` 来创建一个新迭代器</span>
|
|
|
|
|
|
|
|
|
|
不过这些代码会产生一个警告:
|
|
|
|
|
|
|
|
|
|
```console
|
|
|
|
|
{{#include ../listings/ch13-functional-features/listing-13-14/output.txt}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
示例 13-14 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们原因所在:迭代器适配器是惰性的,因此我们需要在此处消费迭代器。
|
|
|
|
|
|
|
|
|
|
为了修复这个警告并消费迭代器,我们将使用第十二章示例 12-1 结合 `env::args` 使用的 `collect` 方法。这个方法消费迭代器并将结果收集到一个集合数据类型中。
|
|
|
|
|
|
|
|
|
|
在示例 13-15 中,我们将遍历由 `map` 调用生成的迭代器结果收集到一个 vector 中。这个 vector 将包含原始 vector 中每个元素加 1 的结果。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-15/src/main.rs:here}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-15:调用 `map` 方法创建一个新迭代器,接着调用 `collect` 方法消费新迭代器并创建一个 vector</span>
|
|
|
|
|
|
|
|
|
|
由于 `map` 接受一个闭包,因此我们可以指定希望在每个元素上执行的任何操作。这是一个很好的例子,展示了如何通过闭包来自定义某些行为,同时复用 `Iterator` trait 提供的迭代行为。
|
|
|
|
|
|
|
|
|
|
可以链式调用多个迭代器适配器来以一种可读的方式进行复杂的操作。不过因为所有的迭代器都是惰性的,你必须调用一个消费适配器方法,才能从这些迭代器适配器的调用中获取结果。
|
|
|
|
|
|
|
|
|
|
### 使用捕获其环境的闭包
|
|
|
|
|
|
|
|
|
|
很多迭代器适配器接受闭包作为参数,而我们通常会指定捕获其环境的闭包作为迭代器适配器的参数。
|
|
|
|
|
|
|
|
|
|
作为一个例子,我们使用 `filter` 方法来获取一个闭包。该闭包从迭代器中获取一项并返回一个 `bool`。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会被包含。
|
|
|
|
|
|
|
|
|
|
示例 13-16 中使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包来遍历一个 `Shoe` 结构体集合。它只会返回指定鞋码的鞋子。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名:src/lib.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust,noplayground
|
|
|
|
|
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-16/src/lib.rs}}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 13-16:使用 `filter` 方法和一个捕获 `shoe_size` 的闭包</span>
|
|
|
|
|
|
|
|
|
|
`shoes_in_size` 函数获取一个鞋子 vector 的所有权和一个鞋码作为参数。它返回一个只包含指定鞋码的鞋子的 vector。
|
|
|
|
|
|
|
|
|
|
`shoes_in_size` 函数体中调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成一个只含有那些闭包返回 `true` 的元素的新迭代器。
|
|
|
|
|
|
|
|
|
|
闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定鞋码的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
|
|
|
|
|
|
|
|
|
|
这个测试展示当调用 `shoes_in_size` 时,返回的只会是与我们指定的鞋码相同的鞋子。
|