|
|
|
@ -1,12 +1,12 @@
|
|
|
|
|
# 迭代器 Iterator
|
|
|
|
|
如果你询问一个 Rust 资深开发:写 Rust 项目最需要掌握什么?相信迭代器往往就是答案之一。无论你是编程新手亦或是高手,实际上大概率都用过迭代器,虽然自己可能并没有意识到这一点:)
|
|
|
|
|
|
|
|
|
|
迭代器允许我们迭代一个连续的集合,例如数组、动态数组Vec、`HashMap`等,在此过程中,只需关心集合中的元素如何处理,而无需去关心如何开始、如何结束、按照什么样的索引去访问等问题。
|
|
|
|
|
迭代器允许我们迭代一个连续的集合,例如数组、动态数组 `Vec`、`HashMap` 等,在此过程中,只需关心集合中的元素如何处理,而无需关心如何开始、如何结束、按照什么样的索引去访问等问题。
|
|
|
|
|
|
|
|
|
|
## For 循环与迭代器
|
|
|
|
|
从用途来看,迭代器跟 `for` 循环颇为相似,都是去遍历一个集合,但是实际上它们存在不小的差别,其中最主要的差别就是:**是否通过索引来访问集合**。
|
|
|
|
|
|
|
|
|
|
例如以下的JS代码就是一个循环:
|
|
|
|
|
例如以下的JS代码就是一个循环:
|
|
|
|
|
```javascript
|
|
|
|
|
let arr = [1, 2, 3];
|
|
|
|
|
for (let i = 0; i < arr.length; i++) {
|
|
|
|
@ -14,7 +14,7 @@ for (let i = 0; i < arr.length; i++) {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在上面代码中,我们设置索引的开始点和结束点,然后再通过索引去访问元素`arr[i]`,这就是典型的循环,来对比下Rust中的`for`:
|
|
|
|
|
在上面代码中,我们设置索引的开始点和结束点,然后再通过索引去访问元素 `arr[i]`,这就是典型的循环,来对比下 Rust 中的 `for`:
|
|
|
|
|
```rust
|
|
|
|
|
let arr = [1, 2, 3];
|
|
|
|
|
for v in arr {
|
|
|
|
@ -42,7 +42,7 @@ for v in arr.into_iter() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
迭代器是函数语言的核心特性,它赋予了Rust远超于循环的强大表达能力,我们也将在本章中一一为大家进行展现。
|
|
|
|
|
迭代器是函数语言的核心特性,它赋予了 Rust 远超于循环的强大表达能力,我们将在本章中一一为大家进行展现。
|
|
|
|
|
|
|
|
|
|
## 惰性初始化
|
|
|
|
|
在Rust中,迭代器是惰性的,意味着如果你不使用它,那么它将不会发生任何事:
|
|
|
|
@ -58,12 +58,12 @@ for val in v1_iter {
|
|
|
|
|
|
|
|
|
|
在 `for` 循环之前,我们只是简单的创建了一个迭代器 `v1_iter`,此时不会发生任何迭代行为,只有在 `for` 循环开始后,迭代器才会开始迭代其中的元素,最后打印出来。
|
|
|
|
|
|
|
|
|
|
这种惰性初始化的方式确保了创建迭代器不会有任何额外的性能损耗,其中的元素也不会被消耗,只有到使用该迭代器的时候,一切才开始。
|
|
|
|
|
这种惰性初始化的方式确保了创建迭代器不会有任何额外的性能损耗,其中的元素也不会被消耗,只有使用到该迭代器的时候,一切才开始。
|
|
|
|
|
|
|
|
|
|
## next 方法
|
|
|
|
|
对于 `for` 如何遍历迭代器,还有一个问题,它如何取出迭代器中的元素?
|
|
|
|
|
|
|
|
|
|
先来看一个特征:
|
|
|
|
|
先来看一个特征:
|
|
|
|
|
```rust
|
|
|
|
|
pub trait Iterator {
|
|
|
|
|
type Item;
|
|
|
|
@ -74,11 +74,11 @@ pub trait Iterator {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
呦,该特征竟然和迭代器iterator同名,难不成。。。没错,它们就是有一腿。**迭代器之所以成为迭代器,就是因为实现了`Iterator`特征**,要实现该特征,最主要的就是实现其中的`next`方法,该方法控制如何从集合中取值,最终返回值的类型是[关联类型](https://course.rs/basic/trait/advance-trait#关联类型)`Item`.
|
|
|
|
|
呦,该特征竟然和迭代器 `iterator` 同名,难不成。。。没错,它们就是有一腿。**迭代器之所以成为迭代器,就是因为实现了 `Iterator` 特征**,要实现该特征,最主要的就是实现其中的 `next` 方法,该方法控制如何从集合中取值,最终返回值的类型是[关联类型](https://course.rs/basic/trait/advance-trait#关联类型) `Item`。
|
|
|
|
|
|
|
|
|
|
因此,之前问题的答案已经很明显:`for` 循环通过不停调用迭代器上的 `next` 方法,来获取迭代器中的元素。
|
|
|
|
|
|
|
|
|
|
既然`for`可以调用`next`方法,是不是意味着我们也可以?来试试:
|
|
|
|
|
既然 `for` 可以调用 `next` 方法,是不是意味着我们也可以?来试试:
|
|
|
|
|
```rust
|
|
|
|
|
fn main() {
|
|
|
|
|
let arr = [1, 2, 3];
|
|
|
|
@ -91,16 +91,16 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
果不其然,将`arr`转换成迭代器后,通过调用其上的`next`方法,我们获取了`arr`中的元素, 有两点需要注意:
|
|
|
|
|
果不其然,将 `arr` 转换成迭代器后,通过调用其上的 `next` 方法,我们获取了 `arr` 中的元素,有两点需要注意:
|
|
|
|
|
|
|
|
|
|
- `next`方法返回的是`Option`类型,当有值时返回`Some(i32)`,无值时返回`None`
|
|
|
|
|
- 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了`Some(1)`,`Some(2)`,`Some(3)`
|
|
|
|
|
- `next` 方法返回的是 `Option` 类型,当有值时返回 `Some(i32)`,无值时返回 `None`
|
|
|
|
|
- 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 `Some(1)`,`Some(2)`,`Some(3)`
|
|
|
|
|
- 手动迭代必须将迭代器声明为 `mut` 可变,因为调用 `next` 会改变迭代器其中的状态数据(当前遍历的位置等),而 `for` 循环去迭代则无需标注 `mut`,因为它会帮我们自动完成
|
|
|
|
|
|
|
|
|
|
总之,`next`方法对**迭代器的遍历是消耗性的**,每次消耗它一个元素,最终迭代器中将没有任何元素, 只能返回`None`.
|
|
|
|
|
总之,`next` 方法对**迭代器的遍历是消耗性的**,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 `None`。
|
|
|
|
|
|
|
|
|
|
#### 例子:模拟实现for循环
|
|
|
|
|
因为for循环是迭代器的语法糖,因此我们完全可以通过迭代器来模拟实现它:
|
|
|
|
|
因为 `for` 循环是迭代器的语法糖,因此我们完全可以通过迭代器来模拟实现它:
|
|
|
|
|
```rust
|
|
|
|
|
let values = vec![1, 2, 3];
|
|
|
|
|
|
|
|
|
@ -122,10 +122,16 @@ let values = vec![1, 2, 3];
|
|
|
|
|
同时我们使用了 `loop` 循环配合 `next` 方法来遍历迭代器中的元素,当迭代器返回 `None` 时,跳出循环。
|
|
|
|
|
|
|
|
|
|
## IntoIterator 特征
|
|
|
|
|
其实有一个细节,由于`Vec`动态数组实现了`IntoIterator`特征,因此可以通过`into_iter`将其转换为迭代器,那如果本身就是一个迭代器,该怎么办?实际上,迭代器自身也实现了`IntoIterator`,标准库早就帮我们考虑好了:
|
|
|
|
|
其实有一个细节,由于 `Vec` 动态数组实现了 `IntoIterator` 特征,因此可以通过 `into_iter` 将其转换为迭代器,那如果本身就是一个迭代器,该怎么办?实际上,迭代器自身也实现了 `IntoIterator`,标准库早就帮我们考虑好了:
|
|
|
|
|
```rust
|
|
|
|
|
impl<I: Iterator> IntoIterator for I {
|
|
|
|
|
// ...
|
|
|
|
|
type Item = I::Item;
|
|
|
|
|
type IntoIter = I;
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
fn into_iter(self) -> I {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@ -149,7 +155,7 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
其实如果以后见多识广了,你会发现这种问题一眼就能看穿,`into_` 之类的,都是拿走所有权,`_mut` 之类的都是可变借用,剩下的就是不可变借用。
|
|
|
|
|
|
|
|
|
|
使用一段代码来解释下:
|
|
|
|
|
使用一段代码来解释下:
|
|
|
|
|
```rust
|
|
|
|
|
fn main() {
|
|
|
|
|
let values = vec![1, 2, 3];
|
|
|
|
@ -164,7 +170,7 @@ fn main() {
|
|
|
|
|
let values = vec![1, 2, 3];
|
|
|
|
|
let _values_iter = values.iter();
|
|
|
|
|
|
|
|
|
|
// 不会报错,因为values_iter是借用了values中的元素
|
|
|
|
|
// 不会报错,因为 values_iter 只是借用了 values 中的元素
|
|
|
|
|
println!("{:?}", values);
|
|
|
|
|
|
|
|
|
|
let mut values = vec![1, 2, 3];
|
|
|
|
@ -180,23 +186,23 @@ fn main() {
|
|
|
|
|
println!("{:?}", values);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
具体解释在代码注释中,就不再赘述,不过有两点需要注意的是:
|
|
|
|
|
具体解释在代码注释中,就不再赘述,不过有两点需要注意的是:
|
|
|
|
|
|
|
|
|
|
- `.iter()` 方法实现的迭代器,调用 `next` 方法返回的类型是 `Some(&T)`
|
|
|
|
|
- `.iter_mut()`方法实现的迭代器,调用`next`方法返回的类型是`Some(&mut T)`, 因此在`if let Some(v) = values_iter_mut.next()`中,`v`的类型是`&mut i32`,最终我们可以通过`*v = 0`的方式修改其值
|
|
|
|
|
- `.iter_mut()` 方法实现的迭代器,调用 `next` 方法返回的类型是 `Some(&mut T)`,因此在 `if let Some(v) = values_iter_mut.next()` 中,`v` 的类型是 `&mut i32`,最终我们可以通过 `*v = 0` 的方式修改其值
|
|
|
|
|
|
|
|
|
|
#### Iterator 和 IntoIterator 的区别
|
|
|
|
|
这两个其实还蛮容易搞混的,但我们只需要记住,`Iterator` 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 `next`。
|
|
|
|
|
|
|
|
|
|
而`IntoIterator`强调的是某一个类型如果实现了该特征,它可以通过`into_iter`,`iter`等方法变成一个迭代器.
|
|
|
|
|
而 `IntoIterator` 强调的是某一个类型如果实现了该特征,它可以通过 `into_iter`,`iter` 等方法变成一个迭代器。
|
|
|
|
|
|
|
|
|
|
## 消费者与适配器
|
|
|
|
|
消费者是迭代器上的方法,它会消费掉迭代器中的元素,然后返回其它类型的值,这些消费者都有一个共同的特点:在它们的定义中,都依赖 `next` 方法来消费元素,因此这也是为什么迭代器要实现 `Iterator` 特征,而该特征必须要实现 `next` 方法的原因。
|
|
|
|
|
|
|
|
|
|
#### 消费者适配器
|
|
|
|
|
只要迭代器上的某个方法`A`在其内部调用了`next`方法,那么`A`就被称为**消费性适配器**: 因为`next`方法会消耗掉迭代器上的元素,可以推出方法`A`的调用也会消耗掉迭代器上的元素。
|
|
|
|
|
只要迭代器上的某个方法 `A` 在其内部调用了 `next` 方法,那么 `A` 就被称为**消费性适配器**:因为 `next` 方法会消耗掉迭代器上的元素,所以方法 `A` 的调用也会消耗掉迭代器上的元素。
|
|
|
|
|
|
|
|
|
|
其中一个例子是`sum`方法,它会拿走迭代器的所有权,然后通过不断调用`next`方法对里面的元素进行求和:
|
|
|
|
|
其中一个例子是 `sum` 方法,它会拿走迭代器的所有权,然后通过不断调用 `next` 方法对里面的元素进行求和:
|
|
|
|
|
```rust
|
|
|
|
|
fn main() {
|
|
|
|
|
let v1 = vec![1, 2, 3];
|
|
|
|
@ -215,7 +221,7 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
如代码注释中所说明的:在使用`sum`方法后,我们将无法再使用`v1_iter`,因为`sum`拿走了该迭代器的所有权:
|
|
|
|
|
如代码注释中所说明的:在使用 `sum` 方法后,我们将无法再使用 `v1_iter`,因为 `sum` 拿走了该迭代器的所有权:
|
|
|
|
|
```rust
|
|
|
|
|
fn sum<S>(self) -> S
|
|
|
|
|
where
|
|
|
|
@ -230,7 +236,7 @@ fn sum<S>(self) -> S
|
|
|
|
|
从 `sum` 源码中也可以清晰看出,`self` 类型的方法参数拿走了所有权。
|
|
|
|
|
|
|
|
|
|
#### 迭代器适配器
|
|
|
|
|
既然消费者适配器是消费掉迭代器,然后返回一个值。那么迭代器适配器,顾名思义,会返回一个新的迭代器,这是实现链式方法调用的关键:`v.iter().map().filter()...`。
|
|
|
|
|
既然消费者适配器是消费掉迭代器,然后返回一个值。那么迭代器适配器,顾名思义,会返回一个新的迭代器,这是实现链式方法调用的关键:`v.iter().map().filter()...`。
|
|
|
|
|
|
|
|
|
|
与消费者适配器不同,迭代器适配器是惰性的,意味着你**需要一个消费者适配器来收尾,最终将迭代器转换成一个具体的值**:
|
|
|
|
|
```rust
|
|
|
|
@ -251,7 +257,7 @@ warning: unused `Map` that must be used
|
|
|
|
|
= note: iterators are lazy and do nothing unless consumed // 迭代器map是惰性的,这里不产生任何效果
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
如上述中文注释所说,这里的`map`方法是一个迭代者适配器,它是惰性的,不产生任何行为,因此我们还需要一个消费者适配器进行收尾:
|
|
|
|
|
如上述中文注释所说,这里的 `map` 方法是一个迭代者适配器,它是惰性的,不产生任何行为,因此我们还需要一个消费者适配器进行收尾:
|
|
|
|
|
```rust
|
|
|
|
|
let v1: Vec<i32> = vec![1, 2, 3];
|
|
|
|
|
|
|
|
|
@ -263,9 +269,9 @@ assert_eq!(v2, vec![2, 3, 4]);
|
|
|
|
|
#### collect
|
|
|
|
|
上面代码中,使用了 `collect` 方法,该方法就是一个消费者适配器,使用它可以将一个迭代器中的元素收集到指定类型中,这里我们为 `v2` 标注了 `Vec<_>` 类型,就是为了告诉 `collect`:请把迭代器中的元素消费掉,然后把值收集成 `Vec<_>` 类型,至于为何使用 `_`,因为编译器会帮我们自动推导。
|
|
|
|
|
|
|
|
|
|
为何`collect`在消费时要指定类型?是因为该方法其实很强大,可以收集成多种不同的集合类型,`Vec<T>`仅仅是其中之一,因此我们必须显式的告诉编译器我们想要收集成的集合类型。
|
|
|
|
|
为何 `collect` 在消费时要指定类型?是因为该方法其实很强大,可以收集成多种不同的集合类型,`Vec<T>` 仅仅是其中之一,因此我们必须显式的告诉编译器我们想要收集成的集合类型(注意此处的类型和前面的类型是有区别的,此处指的是集合类型,上一段中指的是元素类型)。
|
|
|
|
|
|
|
|
|
|
还有一点值得注意,`map`会对迭代器中的每一个值进行一系列操作,然后把该值转换成另外一个新值, 该操作是通过闭包`|x| x + 1`来完成: 最终迭代器中的每个值都增加了`1`,从`[1, 2, 3]`变为`[2, 3, 4]`.
|
|
|
|
|
还有一点值得注意,`map` 会对迭代器中的每一个值进行一系列操作,然后把该值转换成另外一个新值,该操作是通过闭包 `|x| x + 1` 来完成:最终迭代器中的每个值都增加了 `1`,从 `[1, 2, 3]` 变为 `[2, 3, 4]`。
|
|
|
|
|
|
|
|
|
|
再来看看如何使用 `collect` 收集成 `HashMap` 集合:
|
|
|
|
|
```rust
|
|
|
|
@ -279,12 +285,12 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`zip`是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成`Iterator<Item=(ValueFromA, ValueFromB)>` 这样的新的迭代器,在此处就是形如`[(name1, age1), (name2, age2)]`的迭代器。
|
|
|
|
|
`zip` 是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成 `Iterator<Item=(ValueFromA, ValueFromB)>` 这样的新的迭代器,在此处就是形如 `[(name1, age1), (name2, age2)]` 的迭代器。
|
|
|
|
|
|
|
|
|
|
然后再通过 `collect` 将新迭代器中`(K, V)` 形式的值收集成 `HashMap<K, V>`,同样的,这里必须显式声明类型,然后 `HashMap` 内部的 `KV` 类型可以交给编译器去推导,最终编译器会推导出 `HashMap<&str, i32>`,完全正确!
|
|
|
|
|
|
|
|
|
|
#### 闭包作为适配器参数
|
|
|
|
|
之前的`map`方法中,我们使用闭包来作为迭代器适配器的参数,它最大的好处不仅在于就地实现迭代器中元素的处理,还在于可以捕获环境值:
|
|
|
|
|
之前的 `map` 方法中,我们使用闭包来作为迭代器适配器的参数,它最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值:
|
|
|
|
|
```rust
|
|
|
|
|
struct Shoe {
|
|
|
|
|
size: u32,
|
|
|
|
@ -296,7 +302,7 @@ fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`filter`是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数`s`是来自迭代器中的值,然后使用`s`跟外部环境中的`shoe_size`进行比较,若相等,则在迭代器中保留`s`值,若不相等,则从迭代器中剔除`s`值,最终通过`collect`收集为`Vec<Shoe>`类型.
|
|
|
|
|
`filter` 是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 `s` 是来自迭代器中的值,然后使用 `s` 跟外部环境中的 `shoe_size` 进行比较,若相等,则在迭代器中保留 `s` 值,若不相等,则从迭代器中剔除 `s` 值,最终通过 `collect` 收集为 `Vec<Shoe>` 类型。
|
|
|
|
|
|
|
|
|
|
## 实现 Iterator 特征
|
|
|
|
|
之前的内容我们一直基于数组来创建迭代器,实际上,不仅仅是数组,基于其它集合类型一样可以创建迭代器,例如 `HashMap`。 你也可以创建自己的迭代器 - 只要为自定义类型实现 `Iterator` 特征即可。
|
|
|
|
@ -330,7 +336,7 @@ impl Iterator for Counter {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
首先,将该特征的关联类型设置为`u32`,由于我们的计数器保存的`count`字段就是`u32`类型, 因此在`next`方法中,最后返回的是实际上是`Option<u32>`类型.
|
|
|
|
|
首先,将该特征的关联类型设置为 `u32`,由于我们的计数器保存的 `count` 字段就是 `u32` 类型, 因此在 `next` 方法中,最后返回的是实际上是 `Option<u32>` 类型。
|
|
|
|
|
|
|
|
|
|
每次调用 `next` 方法,都会让计数器的值加一,然后返回最新的计数值,一旦计数大于5,就返回 `None`。
|
|
|
|
|
|
|
|
|
@ -347,7 +353,7 @@ assert_eq!(counter.next(), None);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 实现 Iterator 特征的其它方法
|
|
|
|
|
可以看出,实现自己的迭代器非常简单,但是`Iterator`特征中,不仅仅是只有`next`一个方法,那为什么我们只需要实现它呢?因为其它方法都具有[默认实现](https://course.rs/basic/trait/trait.html#默认实现),无需像`next`这样手动去实现,而且这些默认实现的方法其实都是基于`next`方法实现的。
|
|
|
|
|
可以看出,实现自己的迭代器非常简单,但是 `Iterator` 特征中,不仅只有 `next` 一个方法,那为什么我们只需要实现它呢?因为其它方法都具有[默认实现](https://course.rs/basic/trait/trait.html#默认实现),所以无需像 `next` 这样手动去实现,而且这些默认实现的方法其实都是基于 `next` 方法实现的。
|
|
|
|
|
|
|
|
|
|
下面的代码演示了部分方法的使用:
|
|
|
|
|
```rust
|
|
|
|
@ -359,7 +365,7 @@ let sum: u32 = Counter::new()
|
|
|
|
|
assert_eq!(18, sum);
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
其中`zip`, `map`, `filter`是迭代器适配器:
|
|
|
|
|
其中 `zip`,`map`,`filter` 是迭代器适配器:
|
|
|
|
|
|
|
|
|
|
- `zip` 把两个迭代器合并成一个迭代器,新迭代器中,每个元素都是一个元组,由之前两个迭代器的元素组成。例如将**形如** `[1, 2, 3]` 和 `[4, 5, 6]` 的迭代器合并后,新的迭代器形如 `[(1, 4),(2, 5),(3, 6)]`
|
|
|
|
|
- `map` 是将迭代器中的值经过映射后,转换成新的值
|
|
|
|
@ -368,7 +374,7 @@ assert_eq!(18, sum);
|
|
|
|
|
而 `sum` 是消费者适配器,对迭代器中的所有元素求和,最终返回一个 `u32` 值 `18`。
|
|
|
|
|
|
|
|
|
|
##### enumerate
|
|
|
|
|
在之前的流程控制章节,针对`for`循环,我们提供了一种方法可以获取迭代时的索引:
|
|
|
|
|
在之前的流程控制章节,针对 `for` 循环,我们提供了一种方法可以获取迭代时的索引:
|
|
|
|
|
```rust
|
|
|
|
|
let v = vec![1u64, 2, 3, 4, 5, 6];
|
|
|
|
|
for (i,v) in v.iter().enumerate() {
|
|
|
|
@ -396,7 +402,7 @@ println!("{}", val);
|
|
|
|
|
|
|
|
|
|
## 迭代器的性能
|
|
|
|
|
|
|
|
|
|
前面提到,要完成集合遍历,既可以使用for循环也可以使用迭代器,那么二者之间该怎么选择呢,性能有多大差距呢?
|
|
|
|
|
前面提到,要完成集合遍历,既可以使用 `for` 循环也可以使用迭代器,那么二者之间该怎么选择呢,性能有多大差距呢?
|
|
|
|
|
|
|
|
|
|
理论分析不会有结果,直接测试最为靠谱:
|
|
|
|
|
|
|
|
|
@ -449,7 +455,7 @@ mod bench {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
上面的代码对比了for循环和迭代器iterator完成同样的求和任务的性能对比,可以看到迭代器还要更快一点。
|
|
|
|
|
上面的代码对比了 `for` 循环和迭代器 `iterator` 完成同样的求和任务的性能对比,可以看到迭代器还要更快一点。
|
|
|
|
|
|
|
|
|
|
```console
|
|
|
|
|
test bench::bench_for ... bench: 998,331 ns/iter (+/- 36,250)
|
|
|
|
@ -467,7 +473,7 @@ And further: What you do use, you couldn’t hand code any better.
|
|
|
|
|
(翻译一下:用就完事了)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性,可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开(Unrolling)、向量化、消除边界检查等优化手段,使得迭代器和for循环都有极为高效的执行效率。
|
|
|
|
|
总之,迭代器是 Rust 受函数式语言启发而提供的高级语言特性,可以写出更加简洁、逻辑清晰的代码。编译器还可以通过循环展开(Unrolling)、向量化、消除边界检查等优化手段,使得迭代器和 `for` 循环都有极为高效的执行效率。
|
|
|
|
|
|
|
|
|
|
所以请放心大胆的使用迭代器,在获得更高的表达力的同时,也不会导致运行时的损失,何乐而不为呢!
|
|
|
|
|
|
|
|
|
|