迭代器(iterator)负责遍历序列中的每一项并确定序列何时结束的逻辑。
在Rust中迭代器是惰性的(lazy),这意味着它们不会在创建时执行任何操作。考虑以下代码:
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
在上面的代码中,v1.iter()创建了一个迭代器,但它并没有执行任何操作。
下面是的例子中将迭代器和for循环的使用分开:
fn main() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
}
一般来说,我们可能会使用一个从0开始的索引变量,使用这个变量索引vector中的值,并循环增加到元素总量。迭代器提供了对多种不同的虚了使用相同逻辑的灵活性,而不只是像vecter这样课索引的数据结构。
Iteratortrait和next方法🔗
迭代器都实现了一个叫Iterator的trait,这个trait定义像是这样的:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
这里有一个新语法type Item和Self::Item,这是一个关联类型(associated type)。现在只需知道这段代码表明实现了Iteratortrait的类型必须提供一个类型Item,这个类型是迭代器返回的元素类型。
next方法每次返回迭代器中的一个项,封装在Some中,迭代完成时,返回None。下面是next调用的一个例子:
#[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);
}
这里要将v1_iter的类型标注为mut,因为next方法需要改变迭代器的内部状态来更总迭代器在序列中的位置。也就是说,代码消费(consume)了,用尽了迭代器。使用for循环时,无需时v1_iter标注为mut,因为for循环会获取v1_iter的所有权并在后台使其可变。
ℹ️ 注意
从next调用中获取的值时对vector中值的不可变引用,iter方法生成了一个不可变引用的迭代器。如果我们需要一个获取v1所有权并返回拥有所有权的迭代器,可以使用into_iter方法,如果希望迭代可变引用,则可以调用iter_mut方法。
消费迭代器的方法🔗
Iteratortrait定义了很多方法,这些方法都可以用于消费迭代器。一些方法在定义中调用了next方法,这就是为何实现Iteratortrait的类型必须提供next方法的。
这些调用next方法的方法被称为消费适配器(consuming adaptors),因为调用它们会消耗迭代器。一个例子是sum方法,这个方法会获取迭代器的所有权并反复调用next方法,因而会消费迭代器。以下是例子:
#[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了。
产生其他迭代器的方法🔗
Iteratortrait定义了另一种方法,被称为迭代器适配器(iterator adaptors),这些方法不消耗当前迭代器,而是通过改变原始迭代器的某些方面来生成不同迭代器。
map便是一个例子,这个办法获取一个闭包对每个元素进行操作,map方法返回一个新的迭代器,该迭代器生产经过修改的元素。例子如下:
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);
不过这个代码会产生警告,因为实际上这个闭包并没有被调用。为了避免警告,我们可以使用collect方法将迭代器转换为一个集合:
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。因为所有迭代器都是惰性的,所以必须调用消费迭代器的方法,才能从这些迭代器适配器的调用中获取结果。
使用捕获环境的闭包🔗
很多迭代器适配器接受闭包作为参数,而我们通常会指定捕获其环境的闭包作为迭代器适配器参数。
作为例子,我们使用filter方法来获取一个闭包。该闭包从迭代器中获取一项并返回一个bool。如果闭包返回true,其值将会包含在filter提供的新迭代器中。如果返回false,则不会包含在新迭代器中。
下面是代码:
#[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") },
]
)
}
}
shoes_in_size函数体中调用了into_iter来创建一个获取vector所有权的迭代器,然后用filter将这个迭代器适配成一个只含有那些闭包返回true的元素的新迭代器,最后调用collect方法将这个新迭代器转换为vector。