迭代器的使用,自定义迭代器
迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)
负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
在Rust
中,迭代器是 惰性的(lazy)
,这意味着在调用方法使用迭代器之前它都不会有效果。例如我们调用定义于 Vec
上的iter
方法在一个 vector`` v1
上创建了一个迭代器:
fn main() {
let v = vec![1, 2, 3, 4];
let v_iter = v.iter();
}
迭代器被储存在v1_iter
变量中。一旦创建迭代器之后,可以选择用多种方式利用它。我们使用 for
循环来遍历一个数组并在每一个项上执行了一些代码。
将迭代器的创建和for
循环中的使用分开。迭代器被储存在 v_iter
变量中,而这时没有进行迭代。一旦 for
循环开始使用v_iter
,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
fn main() {
let v = vec![1, 2, 3, 4];
let v_iter = v.iter();
for v in v_iter {
println!("{}", v);
}
}
在标准库中没有提供迭代器的语言中,我们可能会使用一个从0
开始的索引变量,使用这个变量索引 vector
中的值,并循环增加其值直到达到 vector
的元素数量。
迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector
这样可索引的数据结构。
迭代器都实现了一个叫做 Iterator
的定义于标准库的 trait
。这个 trait
的定义看起来像这样:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// ....默认实现
}
注意这里有一个我们还未讲到的新语法:type Item
和 Self::Item
,他们定义了 trait
的 关联类型(associated type)
。不过现在只需知道这段代码表明实现 Iterator trait
要求同时定义一个 Item
类型,这个 Item
类型被用作 next
方法的返回值类型。换句话说,Item
类型将是迭代器返回元素的类型。
next
是Iterator
实现者被要求定义的唯一方法。next
一次返回迭代器中的一个项,封装在 Some
中,当迭代器结束时,它返回 None
。
可以直接调用迭代器的 next
方法所得到的值:
fn main() {
let v = vec![1, 2, 3, 4];
let mut v_iter = v.iter();
if let Some(v) = v_iter.next() {
println!("{}", v);
}
if let Some(v) = v_iter.next() {
println!("{}", v);
}
if let Some(v) = v_iter.next() {
println!("{}", v);
}
if let Some(v) = v_iter.next() {
println!("{}", v);
}
if let Some(v) = v_iter.next() {
println!("{}", v);
} else {
println!("结束");
}
}
注意v_iter
需要是可变的:在迭代器上调用next
方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 消费(consume)
了,或使用了迭代器。每一个 next
调用都会从迭代器中消费一个项。使用 for
循环时无需使 v_iter
可变因为 for
循环会获取 v_iter
的所有权并在后台使 v_iter
可变。
另外需要注意到从 next
调用中得到的值是 vector
的不可变引用。iter
方法生成一个不可变引用的迭代器。如果我们需要一个获取 v
所有权并返回拥有所有权的迭代器,则可以调用into_iter
而不是 iter
。类似的,如果我们希望迭代可变引用,则可以调用iter_mut
而不是 iter
:
fn main() {
let mut v = vec![1, 2, 3, 4];
let mut v_iter = v.iter_mut();
if let Some(v) = v_iter.next() {
*v = 5;
}
}
Iterator trait
有一系列不同的由标准库提供默认实现的方法;你可以在 Iterator trait
的标准库API
文档中找到所有这些方法。一些方法在其定义中调用了 next
方法,这也就是为什么在实现Iterator trait
时要求实现 next
方法的原因。
这些调用next
方法的方法被称为 消费适配器(consuming adaptors)
,因为调用他们会消耗迭代器。一个消费适配器的例子是 sum
方法。这个方法获取迭代器的所有权并反复调用 next
来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和:
fn main() {
let v = vec![1, 2, 3, 4];
let v_iter = v.iter();
let s: i32 = v_iter.sum();
println!("{}", s);
}
调用 sum
之后不再允许使用v1_iter
因为调用 sum
时它会获取迭代器的所有权。如果还需要使用则需要重新创建迭代器。
Iterator trait
中定义了另一类方法,被称为 迭代器适配器(iterator adaptors)
,他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
下面是调用迭代器适配器方法 map
的例子,该 map
方法使用闭包来调用每个元素以生成新的迭代器。这里的闭包创建了一个新的迭代器,对其中 vector
中的每个元素都被加 1
。:
fn main() {
let v = vec![1, 2, 3, 4];
v.iter().map(|x| x + 1);
}
不过这些代码会产生一个警告,代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们需要消费迭代器。
也就是说,除非你调用一个消费者,不然,你的操作,永远也不会被调用到!
为了修复这个警告并消费迭代器获取有用的结果,我们将使用 collect
方法。这个方法消费迭代器并将结果收集到一个数据结构中。
我们将遍历由 map
调用生成的迭代器的结果收集到一个 vector
中,它将会含有原始 vector
中每个元素加 1
的结果:
fn main() {
let v = vec![1, 2, 3, 4];
let v1: Vec<_> = v.iter().map(|x| x + 1).collect();
println!("{:?}", v1);
}
因为map
获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。
现在,我们知道了map
,那当然还有filter
,filter
接受一个闭包函数,返回一个布尔值,返回true
的时候表示保留,false
丢弃:
fn main() {
let v = vec![1, 2, 3, 4];
let v1: Vec<_> = v.into_iter().filter(|x| *x < 3).collect();
println!("{:?}", v1);
}
这里注意需要获取所有权的迭代器。
我们可以在 vector
上调用 iter
、into_iter
或iter_mut
来创建一个迭代器,也可以使用标准库中其他的集合类型创建迭代器,如 hashmap
。除此之外,我们可以通过实现Iterator trait
来创建我们自定义的迭代器,并且就像前面我们说的,唯一的要求就是实现next
方法,之后,我们就可以使用其他由 Iterator trait
默认实现的所有方法!
我们将会创建一个从1
到5
进行计数的迭代器来作为例子。首先让我们创建一个自定义的结构体用于存放计数:
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
}
接下来我们为 Counter
实现 Iterator trait
,并实现 next
方法自定义迭代器的行为:
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.count < 5 {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
我们将迭代器的关联类型 Item
设置为 u32
,这代表迭代器的返回值为 u32
类型,并且当count
大于或等于 5
时,next
会返回 None
,迭代结束。
使用我们自定义的迭代器 next
方法:
fn main() {
let mut c = Counter::new();
for _ in 0..10 {
if let Some(v) = c.next() {
println!("{}", v);
} else {
println!("结束");
break;
}
}
}
运行库结果:
1
2
3
4
5
结束
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!