标题
一、迭代器
1.1 概念
- 迭代器(iterator) 是负责遍历序列中的每一项和决定序列何时结束的逻辑;
- Rust中的迭代器是惰性的(lazy),除非调用方法,否则它不会有任何效果;
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter{
println!("Got: {}", val);
}
}
v1_iter
是个迭代器,没有使用的话它不会有任何作用;- 接下来采用for循环打印迭代器里的值;
1.2 Iterator trait和next方法
1) Iterator trait方法
- 所有迭代器都实现了Iterator trait,其定义如下
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
和Self::Item
新语法定义的trait的关联类型(associated type);- 实现 Iterator trait需要同时定义一个Item类型,它是迭代器返回元素的类型;
2) next方法
- next是Iterator实现者被要求定义的唯一方法;
- next方法调用时一次返回迭代器中的一个项,封装在 Some 中,当迭代器结束时,它返回 None;
#[cfg(test)]
mod tests{
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}
}
- 上面的代码测试通过;
- 迭代器
v1_iter
必须是可变的:迭代器上调用next方法改变了迭代器中用来记录序列位置的状态; - 从next调用中得到的值是vector的不可变引用;
- 获取v1所有权并返回拥有所有权的迭代器,使用
into_iter
,迭代可变引用需调用iter_mut
;
1.3 消耗迭代器的方法
- Iterator trait有一些默认的调用了next方法,统称为消耗型适配器(consuming adaptors);
fn main() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
println!("total = {}", total); //total = 6
}
- sum方法就是一种消耗型适配器的方法
- sum会获取迭代器的所有权,因此调用之后不能再使用v1_iter;
1.4 产生其他迭代器的方法
- 迭代器适配器允许将迭代器转换为不同类型的迭代器;
- 迭代器适配器支持链式调用;
#[cfg(test)]
mod tests{
#[test]
fn iterator_demonstration() {
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);
}
}
- iter()方法产生迭代器;
- 再使用map方法使用闭包调用每个元素生成新的迭代器;
- 迭代器对vector中的每个元素都加1;
1.5 使用闭包捕获环境
- filter接收一个闭包;
- 这个闭包在遍历迭代器的每个元素时,返回bool类型;
- 如果闭包返回true,则当前会包含在filter提供的新迭代器中;
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}
#[test]
fn filters_by_size() {
let shoes = vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 13, style: String::from("sandal") },
Shoe { size: 10, style: String::from("boot") },
];
let in_my_size = shoes_in_my_size(shoes, 10);
assert_eq!(
in_my_size,
vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 10, style: String::from("boot") },
]
);
}
shoes.into_iter().filter(|s| s.size == shoe_size).collect()
- 先调用into_iter()方法生成一个迭代器;
- 再调用
filter
方法传入闭包,看鞋号和传入的鞋号是否相等,如果相等则放到新迭代器中; - 最后调用collect()形成集合;
- 最后过滤出10号鞋子,测试通过;
1.6 创建自定义迭代器
- 可以在vector上调用iter、into_iter或iter_mut创建一个迭代器;
- 可以用诸如map的集合类型创建迭代器;
- 可以实现Iterator trait自定义迭代器;
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> {
self.count += 1;
if self.count < 6 {
Some(self.count)
} else {
None
}
}
}
#[test]
fn calling_next_directly() {
let mut counter = Counter::new();
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);
}
创建一个能从1遍历到5的迭代器
- 创建一个Counter结构体,内含迭代过程中的计数变量;
- 为Counter实现next方法,Ttem = u32说明返回值是u32类型;
calling_next_directly
的测试从1到5,最后返回None;
再创建一个迭代器从2开始,即为2,3,4,5,要求产生新的迭代器中的值能被3整除,将满足要求的元素求和
#[test]
fn using_other_iterator_trait_methods() {
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);
}
- 先new一个对象;
- 然后调用zip方法将前后两个迭代器组合到一起,内容形成一个元组,元组的内容分别来源于两个迭代器;
- 使用map将两个元组内的值相乘并产生一个新的迭代器;
- 然后使用filter过滤相乘后能被3整除的元素;
- 最后求和;
计算过程为
- 第一个迭代器值为(1,2,3,4,5)
- 第二个迭代器值为(2,3,4,5)
- zip之后的值为(1,2) (2,3) (3,4)(4,5)
- zip在任一输入迭代器返回None时也返回None,因此没有(5,None)
- 对应相乘为 2,6,12,20,过滤后为6,12;
- 再求和即为18;
二、改进/IO项目
2.1 Config结构体使用迭代器
原始代码
impl Config{
pub fn new(args: &[String]) -> Result<Config, &'static str>{
if args.len() < 3{
return Err("Not enough arguments!");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {query, filename, case_sensitive})
}
}
- 参数args是引用类型,而Config中的query和filename必须有所有权,因此需要克隆出来;
- 现在可以将new函数改为获取一个所有权的迭代器作为参数而不是使用切片;
- 还可以使用迭代器附带的功能进行长度检查和索引;
- 这样可使new的责任范围更加明确(通过迭代器将读取具体值的工作分离出去);
Config::new
一旦获取了迭代器的所有权,就可以将迭代器中的字符串移到Config中,而不用调用clone分配新空间;
修改后代码
impl Config{
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str>{
if args.len() < 3{
return Err("Not enough arguments!");
}
args.next(); //这个表示可执行文件名,这里用不上
let query = match args.next(){
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let filename = match args.next(){
Some(arg) => arg,
None => return Err("Didn't get a file name"),
};
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {query, filename, case_sensitive})
}
}
- 直接使用环境变量的迭代器作为参数,调用
next
得到下一个值; next
之后如果是None则返回错误,否则直接返回字符串;
2.2 main函数
原始代码
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
//其他代码
}
Config::new
需要传入std::env::Args
类型;- 因此不需要执行
collect
收集命令行参数后行成集合,直接传入就行了;
修改
fn main() {
let config = Config::new(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
//其他代码
}
2.3 search函数的修改
原始代码
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{
let mut results = Vec::new();
for line in contents.lines(){
if line.contains(query){
results.push(line);
}
}
results
}
- 可以使用迭代器适配器编写更简明的代码;
- 也避免了可变中间变量results的使用;
修改
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str>{
contents.lines().filter(|line| line.contains(query)).collect()
}