迭代器
类似 JS 中数组的 forEach,map 等迭代方法,在 rust 中,也有迭代器的概念,区别是 rust 的 for_each,map 方法被称之为迭代适配器,迭代适配器是惰性的(lazy)。除非你主动调用,否则它们不会产生任何的实际效果。
创建迭代器
使用 iter 方法创建一个迭代器,并调用 next 进行消费:
let arr = vec![1, 2, 3];
// 创建迭代器
// 由于迭代器内部有维护指针位置,每次迭代会更新指针,所以需要标记mut
let mut iter = v1.iter();
// 消费迭代器
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
// Some(1)
// Some(2)
// Some(3)
// None
复制代码
其实和 JS 中的很像,可以回忆一下:
var arr= [1,2,3];
// 创建迭代器
var iter = arr[Symbol.iterator]();
// 消费迭代器
iter.next();
iter.next();
iter.next();
iter.next();
// {value: 1, done: false}
// {value: 2, done: false}
// {value: 3, done: false}
// {value: undefined, done: true}
复制代码
消费迭代器
除了使用 next 方法手动调用迭代器以外,还可以使用 for 循环来消费,过程中迭代的结果如果是 Some 类型,for 循环会继续迭代,直到遇到 None 才会停止:
let arr = vec![1, 2, 3];
let iter = arr.iter();
// 因为for循环会消费迭代器,所以会取得iter的所有权
for item in iter {
println!("{}", item)
// 1
// 2
// 3
}
println!("{:?}", iter) // 报错,所有权已经被移动
复制代码
迭代器 trait
并不是所有类型都可以进行迭代,能够迭代的类型都需要实现 Iterator trait:
pub trait Iterator {
// 会在后边介绍:关联类型
type Item;
// 每一个迭代器都拥有next方法,
// 它会在每次被调用时返回一个包裹在Some中的迭代器元素,
// 并在迭代结束时返回None。
fn next(&mut self) -> Option<Self::Item>;
}
复制代码
为自定义结构体实现迭代器 trait:
// 定义一个计数器结构体
struct Counter {
// 迭代最大值
max: u32,
// 当前值
current: u32
}
// 实现迭代器trait
impl Iterator for Counter {
// 指定每次迭代的类型
type Item = u32;
// 实现next方法
fn next(&mut self) -> Option<Self::Item>{
// 当前值大于等于最大值时,也就是迭代结束的时候,返回None
if self.current >= self.max {
None
} else {
// 获取当前值
let val = self.current;
// 为下次迭代累加1
self.current += 1;
// 返回当前值
Some(val)
}
}
}
let counter = Counter {
current: 0,
max: 3
};
// 可以直接使用for循环
for item in counter {
println!("{}", item);
// 1
// 2
// 3
}
复制代码
迭代器类型
在消费 iter 迭代器时,我们取到的是每一个成员的不可变引用,事实上一共有三种迭代器可用:
iter:获得成员的不可变引用
iter_mut:获得成员的可变引用
into_iter:获得成员的所有权
消费适配器
rust 内置了许多消费迭代器的方法,这些方法会获取迭代器的所有权并反复调用 next 来遍历元素,例如 sum 方法对数组中的成员求和:
let arr = [1, 2, 3];
let iter = v3.iter();
// sum方法会取得迭代器所有权(可以看一下sum的签名),
// 所以迭代器无法再继续使用
let total: i32 = iter.sum();
println!("{}", total); // 6
复制代码
迭代器适配器
rust 还内置了许多用于通过迭代器链式操作成员的方法,这些方法都是惰性的,只有在最后调用一个消耗适配器的方法后,迭代适配器方法才会执行,例如 map 迭代器适配器:
let arr = [1, 2, 3];
// map好创建一个新的迭代器并返回,
// 由于迭代器适配器是惰性的,除非消费迭代器,否则什么事情都不会发生,
// 因为没有进行消费,此时编译会报提醒
arr.iter().map(|x| x + 1);
复制代码
使用 collect 消费适配器:
// collect会消耗迭代器并将结果值收集到某种集合数据类型中
let arr = [1, 2, 3];
// 注意使用collect需要指定最终类型
let arr2: Vec<i32> = arr.iter().map(|x| x + 1).collect();
// 等价于
let arr2 = arr.iter().map(|x| x + 1).collect::<Vec<i32>>();
println!("{:?}", arr2); // [2, 3, 4]
复制代码
filter 迭代适配器用于过滤成员:
#[derive(Debug)]
struct Shoe {
size: u32
}
let arr = vec![
Shoe { size: 40 },
Shoe { size: 41 },
Shoe { size: 42 },
];
// filter将过滤size大于40的Shoe
let arr2: Vec<_> = arr.iter().filter(|x| x.size > 40).collect();
println!("{:?}", arr2);
// [Shoe { size: 41 }, Shoe { size: 42 }]
复制代码
一个更复杂的迭代适配器例子:
// 使用上面自定义结构体Counter
let sum: u32 = Counter {
current: 0,
max: 5
}
// zip:组合成: (1,2) (2,3) (3,4) (4,5),由于最后一个(5, None)由于其中一个是None,将不会被生成
.zip(Counter {
current: 0,
max: 5
// skip:跳过第一个值,也就是与 1,2,3,4进行zip操作
}.skip(1))
// map映射后:2 6 12 20
.map(|(a, b)| a * b)
// filter过滤后:6 12
.filter(|x| x % 3 == 0)
// sum求和:6 + 12
.sum();
println!("{}", sum); // 18
复制代码
评论