赞
踩
函数式编程风格通常包括将函数作为另一个函数的参数、返回值,将函数作为值赋值给变量,以供后续执行
本章中我们将会介绍以下内容:
闭包:一个可以存储在变量里的类似函数的数据结构
迭代器:一种处理元素序列的方式
如何使用这些功能来改进第十二章的I/O项目
这两个功能的性能(剧透警告:它们的速度超乎你的想象)
我40米的大刀已经饥渴难耐了,让我们攻下这一章!
上一节我们学习了迭代器相关的知识,现在我们来对上述代码进行改进,让其变的更加简洁
使用迭代器并去掉clone
我们先来回顾一下以前的一个例子
struct Config { query:String, filename:String, } 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}) } }
在这里我们使用clone,以便Config结构体可以拥有这些值,但是呢,这样调用是非常低效的
这里需要clone的原因是参数args 是借用的,new函数实际并不拥有它,因此为了返回Config实例的所有权,我们就克隆了Config结构体里面的两个字段
但是,我们学习了迭代器!可以想到是哪个功能可以解决这个问题吗?没错,就是迭代器可以捕获环境,让我们来详细研究一下如何修复这个问题
我们把new函数的参数改为获取一个有所有权的迭代器而不是借用的slice,ok,问题就这么被解决了,我们来看看实际例子
不过,要注意:使用迭代器前检查slice长度和索引特定位置的代码。这会明确Config::new的工作因为迭代器会负责访问这些值
直接使用env::args返回的迭代器
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err| {
eprintln!("Probleming parsing arguments: {}",err);
process::exit(1);
});
}
fn main() {
let config = Config::new(env::args()).unwrap_or_else(|err|{
eprintln!("Problem parsing arguments:{}",err);
process::exit(1);
});
}
将env::args 的返回值传递给Config::new
env::args函数返回一个迭代器!不同于将迭代器的值收集到一个vector中接着传递一个slice给Config::new,现在我们直接将env::args返回的迭代器的所有权传递给Config::new
我们再来更新一下Config::new的定义
pub fn new(mut args: std::env::Args)->Result<Config,&'static str> {
以迭代器作为参数更新Config::new的签名
env::args 函数的标准库文档显示,它返回的迭代器类型为 std::env::Args,因此new函数的参数类型是 std::env::Args 而不是 &[String]。因为我们拥有args的所有权,并且将通过对其进行迭代而改变args,我们可以将mut关键字添加到args参数的规范中以使其可以改变
使用 Iterator trait代替索引
接下来,我们将修改Config::new的内容。因为std::env::Args实现了Iterator trait,因此我们知道可以对其调用next方法
impl Config {
pub fn new(mut args: std::env::Args)->Result<Config,&'static str> {
args.next();
let query = match.next() {
Some(arg) => arg,
None => return Err("Didn't get a query string"),
};
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config{query,filename,case_sensitive})
}
}
注意:env::args返回的第一个值是程序的名称,我们希望忽略它并获取下一个值,所以首先调用next并不对返回值做任何操作
之后对希望放入Config中字段query调用next。如果next返回Some,使用match来提取其值。如果它返回None,则意味着没有提供足够的参数并通过Err值提早返回,对filename值进行同样的操作
使用迭代器适配器来使代码更为简明
I/O项目中其它可以利用迭代器的地方是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 vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变的更容易,因为我们不必管理results vector 的并发访问
pub fn search<'a>(query:&str,contents:&'a &str)->Vec<&'a str>{
contents.lines()
.filter(|line|line.contains(query))
.collect()
}
回忆search函数的目的是返回所有contents中包含query的行,类似于之前filter例子,可以使用filter适配器只保留line.contains(query)返回true的那些行。接着使用collect将匹配收集到另一个vector中,这样就容易多了
接下来的逻辑就是在代码中选用哪种风格问题了,我们大部分工程师还是倾向于使用迭代器风格
下一节我们聊聊这两种实现的性能
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。