迭代器基本特征
迭代器允许我们迭代一个连续的集合,例如数组、动态数组 Vec、HashMap 等,我们只需关注集合中的元素如何处理,而不关心如何去访问等问题。从用途来看,迭代器跟 for 循环颇为相似,都是去遍历一个集合。在rust中,数组不是迭代器,但数组实现了 IntoIterator 特征,Rust 通过 for 语法糖,自动把实现了该特征的数组类型转换为迭代器,最终可以直接对一个数组进行迭代。IntoIterator 特征拥有一个 into_iter 方法,因此可以显式的把数组转换成迭代器:
let arr = [1, 2, 3];
for v in arr.into_iter() { //效果同for v in arr {
println!("{}", v);
}
let v1_iter = v1.iter(); //创建了一个迭代器 v1_iter; 要实现Iterator 特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item。
fn main() {
let arr = [1, 2, 3];
let mut arr_iter = arr.into_iter();
assert_eq!(arr_iter.next(), Some(1));
assert_eq!(arr_iter.next(), Some(2));
assert_eq!(arr_iter.next(), Some(3));
assert_eq!(arr_iter.next(), None);
}
将 arr 转换成迭代器后,通过调用其上的 next 方法,获取了 arr 中的元素,有两点需要注意:
next方法返回的是Option类型,当有值时返回Some(i32),无值时返回None- 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了
Some(1),Some(2),Some(3) - 手动迭代必须将迭代器声明为
mut可变,因为调用next会改变迭代器其中的状态数据(当前遍历的位置等),而for循环去迭代则无需标注mut,因为它会帮我们自动完成
next 方法对迭代器的遍历是消耗性的,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 None。
IntoIterator与Iterator的区别:IntoIterator 强调的是某一个类型如果实现了该特征,它可以通过 into_iter,iter 等方法变成一个迭代器。而Iterator 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next。
从上面可以看出into_iter与iter都可以将数组转换为迭代器,他们的区别如下:
into_iter会夺走所有权,调用next方法返回的类型是Some(T)iter是借用,调用next方法返回的类型是Some(&T)iter_mut是可变借用,调用next方法返回的类型是Some(&mut T)
fn main() {
let values = vec![1, 2, 3];
for v in values.into_iter() {
println!("{}", v)
}
// 下面的代码将报错,因为 values 的所有权在上面 `for` 循环中已经被转移走
// println!("{:?}",values);
let values = vec![1, 2, 3];
let _values_iter = values.iter();
// 不会报错,因为 values_iter 只是借用了 values 中的元素
println!("{:?}", values);
let mut values = vec![1, 2, 3];
// 对 values 中的元素进行可变借用
let mut values_iter_mut = values.iter_mut();
// 取出第一个元素,并修改为0
if let Some(v) = values_iter_mut.next() {
*v = 0;
}
// 输出[0, 2, 3]
println!("{:?}", values);
}
消费者与适配器
消费者是迭代器上的方法,它会消费掉迭代器中的元素,都依赖 next 方法来消费元素,只要迭代器上的某个方法 A 在其内部调用了 next 方法,那么 A 就被称为消费性适配器(消费掉迭代器,然后返回一个值),同理,迭代器适配器就是返回一个新的迭代器,这是实现链式方法调用的关键:v.iter().map().filter()...。与消费者适配器不同,迭代器适配器是惰性的,意味着需要一个消费者适配器来收尾,最终将迭代器转换成一个具体的值:
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum(); //sum就是消费者适配器
assert_eq!(total, 6);
// v1_iter 是借用了 v1,因此 v1 可以照常使用
println!("{:?}",v1);
// 以下代码会报错,因为 `sum` 拿到了迭代器 `v1_iter` 的所有权
// println!("{:?}",v1_iter);
}
let v1: Vec<i32> = vec![1, 2, 3];
//v1.iter().map(|x| x + 1); 这样写会参数告警,因为迭代器适配器map是惰性的,这里不产生任何效果
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
collect方法是一个消费者适配器,它可以将一个迭代器中的元素收集到指定类型中,比如这里为 v2 标注了 Vec<_> 类型,就是为了告诉 collect:请把迭代器中的元素消费掉,然后把值收集成 Vec<_> 类型,至于为何使用 _,因为编译器会帮我们自动推导。
map方法会对迭代器中的每一个值进行一系列操作,然后把该值转换成另外一个新值,该操作是通过闭包 |x| x + 1 来完成:最终迭代器中的每个值都增加了 1,从 [1, 2, 3] 变为 [2, 3, 4]。
以下是使用collect收集成 HashMap 集合的一个示例:
use std::collections::HashMap;
fn main() {
let names = ["sunface", "sunfei"];
let ages = [18, 18];
let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();
println!("{:?}",folks);
}
zip 是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成 Iterator<Item=(ValueFromA, ValueFromB)> 这样的新的迭代器,在此处就是形如 [(name1, age1), (name2, age2)] 的迭代器。然后再通过 collect 将新迭代器中(K, V) 形式的值收集成 HashMap<K, V>
闭包作为适配器参数
之前的 map 方法中,我们使用闭包来作为迭代器适配器的参数,它最大的好处不仅在于可以就地实现迭代器中元素的处理,还在于可以捕获环境值:
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()
}
filter 是迭代器适配器,用于对迭代器中的每个值进行过滤。 它使用闭包作为参数,该闭包的参数 s 是来自迭代器中的值,然后使用 s 跟外部环境中的 shoe_size 进行比较,若相等,则在迭代器中保留 s 值,若不相等,则从迭代器中剔除 s 值,最终通过 collect 收集为 Vec<Shoe> 类型(类型推导,shoes_in_size函数的返回类型)
实现Iterator特征
只要为自定义类型实现 Iterator 特征就可以创建自己的迭代器,例如为计数器实现 Iterator 特征:首先为计数器 Counter 实现一个关联函数 new,用于创建新的计数器实例。
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
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
}
}
}
let mut counter = Counter::new(); //创建Counter实例
//使用新建的Counter进行迭代
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);
Iterator 特征中,不仅仅是只有 next 一个方法,还有其它方法,但都具有默认实现,所以无需像 next 这样手动去实现。
let sum: u32 = Counter::new()
.zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
assert_eq!(18, sum);
其中 zip,map,filter 是迭代器适配器:
zip把两个迭代器合并成一个迭代器,新迭代器中,每个元素都是一个元组,由之前两个迭代器的元素组成。例如将形如[1, 2, 3, 4, 5]和[2, 3, 4, 5]的迭代器合并后,新的迭代器形如[(1, 2),(2, 3),(3, 4),(4, 5)]map是将迭代器中的值经过映射后,转换成新的值[2, 6, 12, 20]filter对迭代器中的元素进行过滤,若闭包返回true则保留元素[6, 12],反之剔除sum是消费者适配器,对迭代器中的所有元素求和,最终返回一个u32值18。
Iterator 特征上的方法 enumerate是一个迭代器适配器,它产生一个新的迭代器,其中每个元素均是元组 (索引,值)
let v = vec![1u64, 2, 3, 4, 5, 6];
let val = v.iter()
.enumerate()
// 每两个元素剔除一个
// [1, 3, 5]
.filter(|&(idx, _)| idx % 2 == 0)
.map(|(_, val)| val)
// 累加 1+3+5 = 9
.fold(0u64, |sum, acm| sum + acm);
println!("{}", val);
迭代器是 Rust 的 零成本抽象,在获得更高的表达力的同时,也不会导致运行时的损失。
424

被折叠的 条评论
为什么被折叠?



