W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
ch13-03-improving-our-io-project.md
commit cc958ca579816ea6ac7e9067d628b0423a1ed3e4
有了這些關(guān)于迭代器的新知識(shí),我們可以使用迭代器來(lái)改進(jìn)第十二章中 I/O 項(xiàng)目的實(shí)現(xiàn)來(lái)使得代碼更簡(jiǎn)潔明了。讓我們看看迭代器如何能夠改進(jìn) Config::new
函數(shù)和 search
函數(shù)的實(shí)現(xiàn)。
在示例 12-6 中,我們?cè)黾恿艘恍┐a獲取一個(gè) String
slice 并創(chuàng)建一個(gè) Config
結(jié)構(gòu)體的實(shí)例,他們索引 slice 中的值并克隆這些值以便 Config
結(jié)構(gòu)體可以擁有這些值。在示例 13-24 中重現(xiàn)了第十二章結(jié)尾示例 12-23 中 Config::new
函數(shù)的實(shí)現(xiàn):
文件名: src/lib.rs
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,
})
}
}
示例 13-24:重現(xiàn)第十二章結(jié)尾的 Config::new
函數(shù)
那時(shí)我們說(shuō)過(guò)不必?fù)?dān)心低效的 clone
調(diào)用了,因?yàn)閷?lái)可以對(duì)他們進(jìn)行改進(jìn)。好吧,就是現(xiàn)在!
起初這里需要 clone
的原因是參數(shù) args
中有一個(gè) String
元素的 slice,而 new
函數(shù)并不擁有 args
。為了能夠返回 Config
實(shí)例的所有權(quán),我們需要克隆 Config
中字段 query
和 filename
的值,這樣 Config
實(shí)例就能擁有這些值。
在學(xué)習(xí)了迭代器之后,我們可以將 new
函數(shù)改為獲取一個(gè)有所有權(quán)的迭代器作為參數(shù)而不是借用 slice。我們將使用迭代器功能之前檢查 slice 長(zhǎng)度和索引特定位置的代碼。這會(huì)明確 Config::new
的工作因?yàn)榈鲿?huì)負(fù)責(zé)訪問(wèn)這些值。
一旦 Config::new
獲取了迭代器的所有權(quán)并不再使用借用的索引操作,就可以將迭代器中的 String
值移動(dòng)到 Config
中,而不是調(diào)用 clone
分配新的空間。
打開(kāi) I/O 項(xiàng)目的 src/main.rs 文件,它看起來(lái)應(yīng)該像這樣:
文件名: src/main.rs
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);
});
// --snip--
}
修改第十二章結(jié)尾示例 12-24 中的 main
函數(shù)的開(kāi)頭為示例 13-25 中的代碼。在更新 Config::new
之前這些代碼還不能編譯:
文件名: src/main.rs
fn main() {
let config = Config::new(env::args()).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {}", err);
process::exit(1);
});
// --snip--
}
示例 13-25:將 env::args
的返回值傳遞給 Config::new
env::args
函數(shù)返回一個(gè)迭代器!不同于將迭代器的值收集到一個(gè) vector 中接著傳遞一個(gè) slice 給 Config::new
,現(xiàn)在我們直接將 env::args
返回的迭代器的所有權(quán)傳遞給 Config::new
。
接下來(lái)需要更新 Config::new
的定義。在 I/O 項(xiàng)目的 src/lib.rs 中,將 Config::new
的簽名改為如示例 13-26 所示。這仍然不能編譯因?yàn)槲覀冞€需更新函數(shù)體:
文件名: src/lib.rs
impl Config {
pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
// --snip--
示例 13-26:以迭代器作為參數(shù)更新 Config::new
的簽名
env::args
函數(shù)的標(biāo)準(zhǔn)庫(kù)文檔顯示,它返回的迭代器的類(lèi)型為 std::env::Args
。我們已經(jīng)更新了 Config :: new
函數(shù)的簽名,因此參數(shù) args
的類(lèi)型為 std::env::Args
而不是 &[String]
。因?yàn)槲覀儞碛?nbsp;args
的所有權(quán),并且將通過(guò)對(duì)其進(jìn)行迭代來(lái)改變 args
,所以我們可以將 mut
關(guān)鍵字添加到 args
參數(shù)的規(guī)范中以使其可變。
接下來(lái),我們將修改 Config::new
的內(nèi)容。標(biāo)準(zhǔn)庫(kù)文檔還提到 std::env::Args
實(shí)現(xiàn)了 Iterator
trait,因此我們知道可以對(duì)其調(diào)用 next
方法!示例 13-27 更新了示例 12-23 中的代碼,以使用 next
方法:
文件名: src/lib.rs
impl Config {
pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
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,
})
}
}
示例 13-27:修改 Config::new
的函數(shù)體來(lái)使用迭代器方法
請(qǐng)記住 env::args
返回值的第一個(gè)值是程序的名稱。我們希望忽略它并獲取下一個(gè)值,所以首先調(diào)用 next
并不對(duì)返回值做任何操作。之后對(duì)希望放入 Config
中字段 query
調(diào)用 next
。如果 next
返回 Some
,使用 match
來(lái)提取其值。如果它返回 None
,則意味著沒(méi)有提供足夠的參數(shù)并通過(guò) Err
值提早返回。對(duì) filename
值進(jìn)行同樣的操作。
I/O 項(xiàng)目中其他可以利用迭代器的地方是 search
函數(shù),示例 13-28 中重現(xiàn)了第十二章結(jié)尾示例 12-19 中此函數(shù)的定義:
文件名: src/lib.rs
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
}
示例 13-28:示例 12-19 中 search
函數(shù)的定義
可以通過(guò)使用迭代器適配器方法來(lái)編寫(xiě)更簡(jiǎn)明的代碼。這也避免了一個(gè)可變的中間 results
vector 的使用。函數(shù)式編程風(fēng)格傾向于最小化可變狀態(tài)的數(shù)量來(lái)使代碼更簡(jiǎn)潔。去掉可變狀態(tài)可能會(huì)使得將來(lái)進(jìn)行并行搜索的增強(qiáng)變得更容易,因?yàn)槲覀儾槐毓芾?nbsp;results
vector 的并發(fā)訪問(wèn)。示例 13-29 展示了該變化:
文件名: src/lib.rs
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
示例 13-29:在 search
函數(shù)實(shí)現(xiàn)中使用迭代器適配器
回憶 search
函數(shù)的目的是返回所有 contents
中包含 query
的行。類(lèi)似于示例 13-19 中的 filter
例子,可以使用 filter
適配器只保留 line.contains(query)
返回 true
的那些行。接著使用 collect
將匹配行收集到另一個(gè) vector 中。這樣就容易多了!嘗試對(duì) search_case_insensitive
函數(shù)做出同樣的使用迭代器方法的修改吧。
接下來(lái)的邏輯問(wèn)題就是在代碼中應(yīng)該選擇哪種風(fēng)格:是使用示例 13-28 中的原始實(shí)現(xiàn)還是使用示例 13-29 中使用迭代器的版本?大部分 Rust 程序員傾向于使用迭代器風(fēng)格。開(kāi)始這有點(diǎn)難以理解,不過(guò)一旦你對(duì)不同迭代器的工作方式有了感覺(jué)之后,迭代器可能會(huì)更容易理解。相比擺弄不同的循環(huán)并創(chuàng)建新 vector,(迭代器)代碼則更關(guān)注循環(huán)的目的。這抽象掉那些老生常談的代碼,這樣就更容易看清代碼所特有的概念,比如迭代器中每個(gè)元素必須面對(duì)的過(guò)濾條件。
不過(guò)這兩種實(shí)現(xiàn)真的完全等同嗎?直覺(jué)上的假設(shè)是更底層的循環(huán)會(huì)更快一些。讓我們聊聊性能吧。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: