目录
- 1. Iterator
- 2. 三种迭代形式
- 3. 实现Iterator
- 4. for循环与IntoIterator
- 5. 按引用迭代
- 6. 适配器
- 7. 惰性
- 8. 无穷
- 参考
1. Iterator
本模块的核心与灵魂是Iterator Trait。Iterator的核心部分如下所示:
traitIterator{typeItem;fnnext(&mutself)->Option<Self::Item>;}迭代器有一个名为next的方法,调用它会返回一个Option<Item>。只要还有元素,调用next就会返回Some(Item);一旦元素全部耗尽,就会返回None,表示迭代已经结束。个别迭代器可以选择恢复迭代,因此之后再调用next,最终也可能在某个时刻重新开始返回Some(Item)。
Iterator的完整定义还包含许多其他方法,但它们都是构建在next之上的默认方法,因此可以免费获得它们。
迭代器还是可组合的,把它们串接起来以完成更复杂的处理是很常见的做法。
2. 三种迭代形式
有三种常见的方法可以从集合创建迭代器:
iter(),遍历产出&T
iter_mut(),遍历产出&mut T
into_iter(),遍历产出T
标准库中的各个类型会在合适的情况下实现这三者中的一个或多个。
3. 实现Iterator
创建你自己的迭代器包含两个步骤:先创建一个struct来保存迭代器的状态,然后为该struct实现Iterator。这正是本模块里有这么多struct的原因:每个迭代器和迭代器适配器各有一个。
示例:
// 首先,定义结构体/// 一个从一数到五的迭代器structCounter{count:usize,}// 我们希望计数从一开始,所以来加一个new()关联函数帮忙// 这并非严格必需,但很方便,注意我们把count初始化为零// 原因会在下文next()的实现中看到implCounter{fnnew()->Self{Self{count:0}}}// 然后,为我们的Counter实现IteratorimplIteratorforCounter{// 我们将用usize来计数typeItem=usize;// next()是唯一必须实现的方法fnnext(&mutself)->Option<Self::Item>{// 让计数加一,这就是我们从零开始的原因self.count+=1;// 检查计数是否已经结束ifself.count<6{Some(self.count)}else{None}}}fnmain(){// 现在可以使用了letmutcounter=Counter::new();println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());}
像这样调用next未免有些重复。Rust有一种语法结构,可以在你的迭代器上不断调用next,直到它返回None。
另请注意,Iterator为nth、fold之类的方法提供了默认实现,这些实现在内部调用next。不过,如果某个迭代器不经过next就能更高效地计算出这类方法的结果,那么也可以为nth、fold之类的方法编写自定义实现。
4. for循环与IntoIterator
Rust的for循环语法实际上是对迭代器的语法糖。下面是一个基本的for示例:
fnmain(){letvalues=vec![1,2,3,4,5];forxinvalues{println!("{x}");}}它会打印1到5这些数字,每个各占一行。不过你会在这里注意到一件事:我们从没在这个vector上调用任何东西来生成迭代器。这是怎么回事?
标准库中有一个用于把某物转换成迭代器的trait:IntoIterator。这个trait有一个方法into_iter,它把实现了IntoIterator的东西转换成一个迭代器。让我们再看一遍那个for循环,以及编译器把它转换成的东西:
fnmain(){letvalues=vec![1,2,3,4,5];forxinvalues{println!("{x}");}}Rust把它脱糖为:
fnmain(){letvalues=vec![1,2,3,4,5];{letresult=matchIntoIterator::into_iter(values){mutiter=>loop{letnext;matchiter.next(){Some(val)=>next=val,None=>break,}letx=next;let()={println!("{x}");};}};result}}首先,我们在值上调用into_iter()。然后,我们对返回的迭代器做match,一遍又一遍地调用next,直到见到None。到那时,我们break跳出循环,遍历就完成了。
这里还有一个微妙之处:标准库中包含一个有趣的IntoIterator实现:
impl<I:Iterator>IntoIteratorforI换句话说,所有Iterator都实现了IntoIterator,方式就是直接返回自身。这带来两件事:
1. 如果你在编写一个Iterator,你可以直接把它用在for循环中。
2. 如果你在创建一种集合,为它实现IntoIterator就能让你的集合配合for循环使用。
5. 按引用迭代
由于into_iter()按值获取self,用for循环遍历一个集合会消耗掉该集合。很多时候,你会想在不消耗集合的前提下遍历它。许多集合提供了一些方法,用来产出遍历引用的迭代器,按惯例分别叫做iter()和iter_mut():
fnmain(){letmutvalues=vec![41];forxinvalues.iter_mut(){*x+=1;}forxinvalues.iter(){println!("{x}");}println!("{}",values.len());}如果某个集合类型C提供了iter(),它通常也会为&C实现IntoIterator,其实现仅仅是调用iter()。类似地,提供了iter_mut()的集合C一般会通过委托给iter_mut()来为&mut C实现IntoIterator。这就带来了一种便捷的简写:
fnmain(){letmutvalues=vec![41];forxin&mutvalues{*x+=1;}forxin&values{println!("{x}");}println!("{}",values.len());}虽然许多集合都提供iter(),但并非所有集合都提供iter_mut()。例如,修改HashSet<T>的键,可能会在键的哈希值发生变化后使集合进入不一致的状态,因此这种集合只提供iter()。
6. 适配器
接受一个Iterator并返回另一个Iterator的函数,通常被称为迭代器适配器,因为它们是适配器模式的一种形式。
常见的迭代器适配器包括map、take和filter。
如果某个迭代器适配器发生了panic,该迭代器将处于未指定但内存安全的状态。并且这个状态也不保证在不同版本的Rust之间保持一致,因此你应当避免依赖一个发生过panic的迭代器后续返回的确切值。
7. 惰性
迭代器以及迭代器适配器是惰性的。这意味着仅仅创建一个迭代器并不会做太多事情。在调用next之前,实际上什么都不会发生。当人们只为副作用而创建迭代器时,这一点有时会造成困惑。例如,map方法会在它遍历到的每个元素上调用一个闭包:
fnmain(){letv=vec![1,2,3,4,5];v.iter().map(|x|println!("{x}"));}这不会打印任何值,因为我们只是创建了迭代器,而没有使用它。编译器会对这种行为发出警告:
为副作用而写map的惯用方式是使用for循环,或者调用for_each方法:
fnmain(){letv=vec![1,2,3,4,5];v.iter().for_each(|x|println!("{x}"));// 或者forxin&v{println!("{x}");}}另一种常见的求值迭代器的方式,是使用collect方法产出一个新集合。
8. 无穷
迭代器不必是有限的。例如,一个开放式区间就是一个无穷迭代器:
fnmain(){letnumbers=0..;}通常的做法是使用take迭代器适配器,把无限迭代器变成有限的:
fnmain(){letnumbers=0..;letfive_numbers=numbers.take(5);fornumberinfive_numbers{println!("{number}");}}这会打印0到4这些数字,每个各占一行。
请记住:在无限迭代器上调用方法时——即使是那些数学上能在有限时间内确定结果的方法——也可能不会终止。具体来说,诸如min这类一般需要遍历迭代器中每一个元素的方法,对任何无限迭代器都不太可能成功返回。
fnmain(){letones=std::iter::repeat(1);letleast=ones.min().unwrap();// 糟糕!无限循环!// ones.min()会造成无限循环,因此执行不到这一行!println!("The smallest number one is {least}.");}参考
1、Rust std::iter