使用迭代器处理一系列项目
迭代器模式允许您对 转。迭代器负责迭代每个项目的逻辑,并且 确定序列何时完成。当您使用迭代器时,您不会 必须自己重新实现该逻辑。
在 Rust 中,迭代器是惰性的,这意味着它们在你调用
使用迭代器来用完它的方法。例如,中的
示例 13-10 在 vector 中的项目上创建一个迭代器v1
通过调用
这iter
method defined onVec<T>
.此代码本身不执行任何作
有用。
fn main() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); }
迭代器存储在v1_iter
变量。创建
iterator 中,我们可以以多种方式使用它。在第 3 章的示例 3-5 中,我们
使用for
循环在其每个
项目。在后台,这隐式创建并使用了一个迭代器
但直到现在,我们才掩盖了它究竟是如何工作的。
在示例 13-11 的示例中,我们将迭代器的创建与
在for
圈。当for
循环使用
中的 iteratorv1_iter
,则迭代器中的每个元素都用于一个
迭代,打印出每个值。
fn main() { let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("Got: {val}"); } }
for
圈在没有标准库提供的迭代器的语言中, 你可能会通过在 index 处启动一个变量来编写相同的功能 0,使用该变量索引向量以获取值,以及 在循环中递增变量值,直到达到 向量中的项目。
迭代器为您处理所有这些逻辑,从而减少重复的代码 可能会搞砸。迭代器为您提供了更大的灵活性来使用相同的 logic 具有许多不同类型的序列,而不仅仅是数据结构 index into 的 like 向量。让我们看看迭代器是如何做到这一点的。
这Iterator
trait 和next
方法
所有迭代器都实现了一个名为Iterator
,在
标准库。trait 的定义如下所示:
#![allow(unused)] fn main() { pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; // methods with default implementations elided } }
请注意,此定义使用了一些新语法:type Item
和Self::Item
,
它们定义了与此 trait 关联的类型。我们将讨论
关联类型在第 19 章中深入介绍。现在,您需要知道的是
这段代码说实现Iterator
trait 要求您还定义
一Item
type 和 thisItem
type 用于next
方法。换句话说,Item
type 将从
迭 代。
这Iterator
trait 只需要实现者定义一个方法:next
方法,该方法一次返回 Iterator 的一个项目,包装在Some
并且,当迭代结束时,返回None
.
我们可以调用next
method 直接对迭代器执行;示例 13-12 演示了
重复调用next
在创建的迭代器
从向量。
#[cfg(test)]
mod tests {
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}
}
next
method请注意,我们需要将v1_iter
mutable:调用next
方法在
iterator 更改 iterator 用于跟踪 where 的内部状态
它在序列中。换句话说,此代码会消耗或用完
迭 代。每次调用next
吃掉 Iterator 中的项。我们不需要
使v1_iter
mutable 时,当我们使用for
循环,因为循环占用了
所有权v1_iter
并使其在幕后可变。
还要注意,我们从对next
是不可变的
对向量中值的引用。这iter
方法生成一个迭代器
over 不可变引用。如果我们想创建一个迭代器,它采用
所有权v1
并返回拥有的值,我们可以调用into_iter
而不是iter
.同样,如果我们想迭代可变引用,我们可以调用iter_mut
而不是iter
.
使用 Iterator 的方法
这Iterator
trait 有许多不同的方法,其中 default
标准库提供的实现;您可以了解这些
方法,方法是查看标准库 API 文档中的Iterator
特性。其中一些方法调用next
method 的定义中,其中
是您需要实现next
方法,在实现Iterator
特性。
调用next
称为 consuming adapters,因为调用它们
用完了迭代器。一个例子是sum
方法,该方法获取
迭代器,并通过重复调用next
因此
使用 iterator。当它迭代时,它会将每个项目添加到正在运行的
total 并在迭代完成时返回 total。示例 13-13 有一个
test 演示了sum
方法:
#[cfg(test)]
mod tests {
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}
}
sum
获取迭代器中所有项目的总数的方法我们不允许使用v1_iter
调用sum
因为sum
需要
我们调用它的迭代器的所有权。
生成其他迭代器的方法
Iterator adapters 是在Iterator
不
使用 iterator。相反,它们通过更改
原始迭代器的某些方面。
示例 13-14 显示了调用 iterator 适配器方法的示例map
,
它需要一个 Closure 来在迭代 Item 时调用每个 Item。
这map
method 返回生成修改后的项的新迭代器。这
this 中的 closure 创建一个新的迭代器,其中 vector 中的每个项目都将是
递增 1:
fn main() { let v1: Vec<i32> = vec![1, 2, 3]; v1.iter().map(|x| x + 1); }
map
创建新的迭代器但是,此代码会生成警告:
$ cargo run
Compiling iterators v0.1.0 (file:///projects/iterators)
warning: unused `Map` that must be used
--> src/main.rs:4:5
|
4 | v1.iter().map(|x| x + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: iterators are lazy and do nothing unless consumed
= note: `#[warn(unused_must_use)]` on by default
help: use `let _ = ...` to ignore the resulting value
|
4 | let _ = v1.iter().map(|x| x + 1);
| +++++++
warning: `iterators` (bin "iterators") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/iterators`
示例 13-14 中的代码什么都没做;我们指定的 从来没有被叫到。该警告提醒我们原因:iterator 适配器是惰性的,并且 我们需要在这里使用 iterator。
要修复此警告并使用迭代器,我们将使用collect
方法
我们在第 12 章中使用了它env::args
在示例 12-1 中。此方法
使用 iterator 并将结果值收集到集合数据中
类型。
在示例 13-15 中,我们收集了迭代器的结果,该迭代器是
从对map
转换为向量。这个向量最终会得到
包含原始向量中递增 1 的每个项目。
fn main() { let v1: Vec<i32> = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]); }
map
方法来创建新的迭代器,然后调用collect
使用新迭代器并创建 vector 的方法因为map
接受一个闭包,我们可以指定要执行的任何作
在每个项目上。这是一个很好的例子,说明闭包如何让你自定义一些
行为,同时重用迭代行为,则Iterator
特性
提供。
您可以将多个调用链接到迭代器适配器,以在 一种可读的方式。但是因为所有的迭代器都是惰性的,所以你必须调用 使用 Adapter 方法从对 Iterator Adapter 的调用中获取结果。
使用捕获其环境的闭包
许多迭代器适配器将闭包作为参数,通常是闭包 我们将指定 as 参数 iterator adapters 将是捕获 他们的环境。
在此示例中,我们将使用filter
方法。这
closure 从迭代器中获取一个项目并返回一个bool
.如果
返回true
,该值将包含在由filter
.如果 closure 返回false
,则不会包含该值。
在示例 13-16 中,我们使用filter
使用一个闭包来捕获shoe_size
变量来迭代Shoe
结构
实例。它将仅返回指定尺码的鞋子。
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn filters_by_size() {
let shoes = vec![
Shoe {
size: 10,
style: String::from("sneaker"),
},
Shoe {
size: 13,
style: String::from("sandal"),
},
Shoe {
size: 10,
style: String::from("boot"),
},
];
let in_my_size = shoes_in_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe {
size: 10,
style: String::from("sneaker")
},
Shoe {
size: 10,
style: String::from("boot")
},
]
);
}
}
filter
方法,其中包含一个闭包,该闭包捕获shoe_size
这shoes_in_size
函数获得 Shoes 和 Shoe 的向量的所有权
size 作为参数。它返回一个向量,该向量仅包含指定
大小。
在 bodyshoes_in_size
,我们调用into_iter
创建迭代器
,这需要向量的所有权。然后我们调用filter
以适应
iterator 转换为新的迭代器,该迭代器仅包含闭包的
返回true
.
闭包捕获shoe_size
parameter 和
将该值与每只鞋的尺码进行比较,仅保留该尺码的鞋子
指定。最后,调用collect
收集
adaptediterator 转换为函数返回的 vector。
测试表明,当我们调用shoes_in_size
,我们只得到鞋子
的大小与我们指定的值相同。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准