15.2.使用迭代器处理一系列子项
迭代器模式允许你依次对一系列成员项执行相应的任务。一个迭代器负责遍历每一个成员,并决定何时结束。当你使用迭代器,你不用自己重新实现这套逻辑。
在Rust中,迭代器是惰性加载,这意味着知道你使用的方法中调用了迭代器,直至结束,迭代器才会投入工作。例如:下面的代码针对向量v1通过使用iter方法创建了迭代器,这个方法隶属于Vec<T>向量数组。这段代码并没有做任何有用的工作。
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
迭代器保存在v1_iter变量中。一旦创建了迭代器,我们可以按照不同的方式来使用它。之前的一些代码中我们使用for循环来对每一个成员项执行相应的操作。在这层代码之下隐含着,它创建并消费了一个迭代器,并没有详细解释其工作原理。
下面的示例中,我们将创建迭代器和使用迭代器分离,当for循环被调用时,使用了迭代器v1_iter,在迭代器中的每一个元素被使用在每一次循环迭代中,它会打印出每一个元素。
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {val}");
}
在那些没有在标准库中提供迭代器的编程语言中,你需要自己来实现该功能,首先需要一个变量来保存索引值,该索引值从0开始,然后使用数组加索引值的方式获取对应的数据,然后增加索引值,获取不同的对应值,一直循环到数组的索引的最大值。
迭代器为你处理所有这些逻辑,取出了你可能会无意中添加的冗余代码。迭代器在为不同种类的序列值使用相同的逻辑,并给了你更多的灵活性,不仅仅包括可以添加索引的那些数据结构,比如向量。下面让我们来看一下迭代器是如何工作的。
15.2.1 迭代器接口和next方法
所有的迭代器都实现了Iterator接口,它定义在标准库中。这个接口的定义规范如下所示:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// methods with default implementations elided
}
注意:上面的代码中使用了新的语法:type Item和Self::Item,它使用接口定义了一个关联类型。后面会深度解析关联类型。本节我们需要知道的是这段代码实现了Iterator接口,它定义了一个Item类型,这个类型用户使用next方法返回Item类型的值。换而言之,Item类型是迭代器必须返回的类型。
Iterator接口只需要实现一个方法:next方法,每次调用该方法返回一个成员,这个成员包裹在Some中,如果迭代器循环结束,返回None。
我们可以直接调用迭代器的next方法;下面代码演示了重复调用向量数组迭代器的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需要设置为可变的:调用next方法会修改迭代器内部的状态,这个状态会跟踪它在序列中的位置。代码会消耗或消耗完这个迭代器。每次调用next方法会消耗掉迭代器中的一个成员项。当使用for循环时,我们不需要将v1_iter设置为可变的,因为它会占用v1_iter的所有权并在其内部会设置为可变的。
还需要注意的是,我们从next获取的返回值时向量数组的成员项的不可变引用。iter方法会产生一个迭代器,并返回值的不可变引用。如果我们创建的迭代器占据了它的所有权,那么它会返回有所有权的返回值。into_iter就是干这个事的。同样,如果我们想要使用迭代器返回可变引用的返回值,可以使用iter_mut方法。
15.2.2 使用迭代器的其他方法
标准库还未Iterator接口提供了许多已经默认实现的方法。查看标准库API你可以获取详细信息。这些方法中有些回调用next方法,这就是为什么我们在实现Iterator接口是需要自己实现next方法。
调用next的方法称之为消费性适配器,因为调用它们会耗尽迭代器。例如:sum方法,它会占据迭代器的所有权,并且使用迭代器连续调用next方法,直至遍历完整个序列。当它在遍历每个成员项时,它会累计成员项的数值,循环结束后,返回最终的累加值。下面的示例示范了sum的使用。
fn main() {
let v1 = vec![1,2,3];
let v1_iter = v1.iter();
let total:i32 = v1_iter.sum();
println!("合计值:{total}");
}
我们允许v1_iter直接调用sum方法来获取汇总值,因为sum会在调用迭代器时获取其所有权。
15.2.3 产生另一个迭代器的方法
迭代器适配器时Iterator中不消耗迭代器的方法。相反,它们通过修改原始迭代器的某些方面来产生不同的迭代器。
下面的代码示例调用一个map方法,它使用了闭包作为参数逐项处理它的成员项。map方法会返回一个新的迭代器,包含了修改过的成员项序列。示例中创建了一个新的迭代器,它的成员项会在旧成员项上加1。
fn main() {
let v1 = vec![1, 2, 3];
let v2 = v1.iter().map(|x| x + 1);
println!("新的迭代器的值:{v2:?}");
}
产生的新的迭代器v2在代码中并没有什么用,因为该迭代器并没有消费,为了消费它,我们使用collect方法,这个方法会循环迭代器并将获取的返回值存入一个集合类型的变量中。
fn main() {
let v1 = vec![1, 2, 3];
let v2:Vec<_> = v1.iter().map(|x| x + 1).collect();
println!("新的迭代器的值:{v2:?}");
}
因为map使用闭包作为参数,因此我们可以对成员项做任何我们想要做的操作。这就示范了使用迭代器产生的新迭代器,让我们可以自定义一些行为。
我们可以以一种只读的方式,进行链式调用迭代器适配器来执行非常复杂的一些操作。但是由于迭代器是惰性执行,你必须最后调用一个消费性适配器方法来消耗迭代器获取最终的结果。
15.2.4 捕获环境的闭包
许多迭代器适配器都采用闭包作为参数,常见的是使用闭包捕获作用域。
例如:使用filter方法就采用闭包作为参数。这个闭包获取迭代器中的成员项并返回布尔值。如果闭包返回真,这个成员项就被包含在有filter新产生的迭代器中,如果返回假,该成员项就不会包含进来。
下面的示例中,我们创建了Shoe结构数组实例,它使用filter捕获指定shoe_size的Shoe成员,最后它会返回符合要求的那些鞋子。
#[derive(PartialEq,Debug)]
struct Shoe {
size:u32,
style: String,
}
pub 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创建迭代器并带有所有权。然后调用filter来过滤符合条件的元素并产生新的迭代器,最终使用collect方法来产生新的数组并返回。
闭包捕获作用域内shoe_size变量,并和每一双鞋子的尺寸进行比较,符合要求的返回,不符合的忽视,最终使用collect方法来产生新的数组并返回。
这个代码展示了,当调用shoes_in_size时,我们只返回符合指定鞋子尺寸的哪些鞋子。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)