Rust 迭代器
迭代器(iterator)就是能一个接一个地产生一串值的东西。其实从 for 循环那一页开始,你就一直在用它,只是还没叫出它的名字:每个遍历集合的 for 循环,背后都是迭代器在干活。
fn main() { let numbers = vec![1, 2, 3]; let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1) println!("{:?}", iter.next()); // Some(2) println!("{:?}", iter.next()); // Some(3) println!("{:?}", iter.next()); // None}输出:
Some(1)Some(2)Some(3)None.next() 返回一个 Option:还有元素时返回 Some(值),元素用完了就返回 None。for 循环说穿了,就是在背后不停地调用 .next(),直到拿到 None 为止。
map:转换每一个元素
Section titled “map:转换每一个元素”.map() 接收一个闭包,把它应用到每个元素上,产生一个新的迭代器:
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}输出:
[2, 4, 6, 8, 10].collect() 负责把迭代器重新变回一个具体的集合(这里是 Vec<i32>)。少了它,你手里就只有一个还没开始计算的迭代器,什么也不会发生。
filter:只留下一部分元素
Section titled “filter:只留下一部分元素”fn main() { let numbers = vec![1, 2, 3, 4, 5, 6]; let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);}输出:
[2, 4, 6]两个星号的 **n 看起来有点怪。这是因为 .filter() 交给闭包的是“引用的引用”(&&i32),要和普通数字比较,就得解引用两次。这是个常见的小别扭,现在不用急着弄懂背后的全部原因。
把 map 和 filter 串起来
Section titled “把 map 和 filter 串起来”链式调用迭代器方法的好处,从这里开始显现:几个步骤可以合成一条清晰易读的流水线。
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers .iter() .filter(|n| **n % 2 == 0) // 只保留偶数 .map(|n| n * 10) // 每个都乘以 10 .collect();
println!("{:?}", result);}输出:
[20, 40, 60]可以和手写的版本比一比:用 for 循环,再往一个空 Vec 里 push。那样写也没错,但链式写法读起来就是“先筛选,再乘以 10,最后收集”,和你脑子里想的步骤一模一样。
其他几个常用方法
Section titled “其他几个常用方法”fn main() { let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum(); let count = numbers.iter().count(); let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("总和:{total},个数:{count},有偶数:{has_even}");}输出:
总和:15,个数:5,有偶数:true.sum() 把所有元素加起来,.count() 告诉你一共有几个元素,.any() 回答“是不是至少有一个元素满足条件”。这些只是每个迭代器都有的方法里的一小部分,还有很多,比如 enumerate、zip、take 和 rev。在自定义迭代器那一页,你会亲手写一个迭代器,并看到它白白就拥有了所有这些方法。
1. 奇数的平方和
Section titled “1. 奇数的平方和”取 1 到 10 这几个数,只保留奇数,把每个数平方,再全部加起来。要求用一条迭代器链完成,不用任何 mut 变量。提示:把 .filter()、.map() 和 .sum() 串起来,并给结果标上类型,比如 let total: i32。
查看答案
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers .iter() .filter(|n| **n % 2 == 1) // 保留 1, 3, 5, 7, 9 .map(|n| n * n) // 1, 9, 25, 49, 81 .sum();
println!("奇数的平方和:{total}");}输出:
奇数的平方和:165total 上必须写 : i32,因为 .sum() 可以加总成好几种数字类型,Rust 猜不出你想要哪一种。
2. 把长单词大声喊出来
Section titled “2. 把长单词大声喊出来”从 vec!["the", "quick", "brown", "fox", "jumps"] 开始,构建一个新的 Vec<String>,里面只放长度超过 3 个字母的单词,并且全部转成大写,然后用 {:?} 打印出来。(这道题要按长度筛选、再转大写,所以单词保留英文:一个汉字在 UTF-8 里占 3 个字节,也没有大小写之分。)提示:.filter(|w| w.len() > 3) 留下长单词,.to_uppercase() 把 &str 转成大写的 String。
查看答案
fn main() { let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words .iter() .filter(|w| w.len() > 3) .map(|w| w.to_uppercase()) .collect();
println!("{:?}", loud);}输出:
["QUICK", "BROWN", "JUMPS"]words 本身没有任何变化。.iter() 只是借用它,所以这条链之后你照样可以使用 words。
- 迭代器通过
.next()一次产生一个值,.next()返回的是Option。 for循环的背后就是迭代器。.map()转换每个元素;.filter()只保留满足条件的元素;.collect()把结果变回一个具体的集合。- 把迭代器方法串成链,通常比手写循环、再用一个可变变量累加结果更清楚。
小测验
1. 迭代器的 .next() 返回什么?
2. 在 .iter().map(...).collect() 这样的链里,最后的 .collect() 做了什么?
3. 在 .filter(|n| **n % 2 == 0) 里,为什么要对 n 解引用两次?
4. for 循环遍历集合时,背后靠的是什么?
5. .filter() 之后的迭代器里会留下哪些元素?
得分: 0 / 5
- 使用迭代器处理元素序列 — Rust 程序设计语言:官方教程对
next、map、filter和collect的介绍,例子更多。 - 性能比较:循环对迭代器 — Rust 程序设计语言:为什么迭代器链和手写循环一样快。
Iteratortrait — 标准库文档(英文):迭代器所有方法的完整列表,每个都配有小例子,很值得随手翻翻。- Iterator — 通过例子学 Rust:一个简短、可运行的例子,实现了斐波那契数列迭代器。