# 迭代器
> [iterators.md](https://github.com/rust-lang/rust/blob/master/src/doc/book/iterators.md)
commit 6ba952020fbc91bad64be1ea0650bfba52e6aab4
讓我們討論一下循環。
還記得Rust的`for`循環嗎?這是一個例子:
~~~
for x in 0..10 {
println!("{}", x);
}
~~~
現在我們更加了解Rust了,我們可以談談這里的具體細節了。這個范圍(`0..10`)是“迭代器”。我們可以重復調用迭代器的`.next()`方法,然后它會給我們一個數據序列。
就像這樣:
~~~
let mut range = 0..10;
loop {
match range.next() {
Some(x) => {
println!("{}", x);
},
None => { break }
}
}
~~~
我們創建了一個`range`的可變綁定,它是我們的迭代器。我們接著`loop`,它包含一個`match`。`match`用來匹配`range.next()`的結果,它給我們迭代器的下一個值。`next`返回一個`Option<i32>`,在這個例子中,如果有值,它會返回`Some(i32)`然后當我們循環完畢,就會返回`None`。如果我們得到`Some(i32)`,我們就會打印它,如果我們得到`None`,我們`break`出循環。
這個代碼例子基本上和我們的`loop`版本一樣。`for`只是`loop`/`match`/`break`結構的簡便寫法。
然而,`for`循環并不是唯一使用迭代器的結構。編寫你自己的迭代器涉及到實現`Iterator`特性。然而特性不是本章教程的涉及范圍,不過Rust提供了一系列的有用的迭代器幫助我們完成各種任務。但首先注意下*范圍* 的一些局限性。
*范圍* 非常原始,我們通常可以用更好的替代方案。考慮下面的 Rust 反模式:用*范圍* 來模擬 C-風格的`for`循環。比如你想遍歷完 vector 的內容。你可能嘗試這么寫:
~~~
let nums = vec![1, 2, 3];
for i in 0..nums.len() {
println!("{}", nums[i]);
}
~~~
這嚴格的說比使用現成的迭代器還要糟。你可以直接在 vector 上遍歷。所以這么寫:
~~~
let nums = vec![1, 2, 3];
for num in &nums {
println!("{}", num);
}
~~~
這么寫有兩個原因。第一,它更明確的表明了我們的意圖。我們迭代整個向量,而不是先迭代向量的索引,再按索引迭代向量。第二,這個版本也更有效率:第一個版本會進行額外的邊界檢查因為它使用了索引,`nums[i]`。因為我們利用迭代器獲取每個向量元素的引用,第二個例子中并沒有邊界檢查。這在迭代器中非常常見:我們可以忽略不必要的邊界檢查,不過仍然知道我們是安全的。
這里還有一個細節不是100%清楚的就是`println!`是如何工作的。`num`是`&i32`類型。也就是說,它是一個`i32`的引用,并不是`i32`本身。`println!`為我們處理了解引用,所以我們并沒有看到它。下面的代碼也能工作:
~~~
let nums = vec![1, 2, 3];
for num in &nums {
println!("{}", *num);
}
~~~
現在我們顯式的解引用了`num`。為什么`&nums`會給我們一個引用呢?首先,因為我們顯式的使用了`&`。再次,如果它給我們數據,我們就是它的所有者了,這會涉及到生成數據的拷貝然后返回給我們拷貝。通過引用,我們只是借用了一個數據的引用,所以僅僅是傳遞了一個引用,并不涉及數據的移動。
那么,既然現在我們已經明確了范圍通常不是我們需要的,讓我們來討論下你需要什么。
這里涉及到大體上相關的3類事物:迭代器,*迭代適配器*(*iterator adapters*)和*消費者*(*consumers*)。下面是一些定義:
- *迭代器* 給你一個值的序列
- *迭代適配器* 操作迭代器,產生一個不同輸出序列的新迭代器
- *消費者* 操作迭代器,產生最終值的集合
讓我們先看看消費者,因為我們已經見過范圍這個迭代器了。
### 消費者(Consumers)
*消費者* 操作一個迭代器,返回一些值或者幾種類型的值。最常見的消費者是`collect()`。這個代碼還不能編譯,不過它表明了我們的意圖:
~~~
let one_to_one_hundred = (1..101).collect();
~~~
如你所見,我們在迭代器上調用了`collect()`。`collect()`從迭代器中取得盡可能多的值,然后返回結果的集合。那么為什么這不能編譯呢?因為Rust不能確定你想收集什么類型的值,所以你需要讓它知道。下面是一個可以編譯的版本:
~~~
let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
~~~
如果你還記得,`::<>`語法允許我們給出一個類型提示,所以我們可以告訴編譯器我們需要一個整型的向量。但是你并不總是需要提供完整的類型。使用`_`可以讓你提供一個部分的提示:
~~~
let one_to_one_hundred = (1..101).collect::<Vec<_>>();
~~~
這是指“請把值收集到`Vec<T>`,不過自行推斷`T`類型”。為此`_`有時被稱為“類型占位符”。
`collect()`是最常見的消費者,不過這還有其它的消費者。`find()`就是一個:
~~~
let greater_than_forty_two = (0..100)
.find(|x| *x > 42);
match greater_than_forty_two {
Some(_) => println!("Found a match!"),
None => println!("No match found :("),
}
~~~
`find`接收一個閉包,然后處理迭代器中每個元素的引用。如果這個元素是我們要找的,那么這個閉包返回`true`,如果不是就返回`false`。因為我們可能不能找到任何元素,所以`find`返回`Option`而不是元素本身。
另一個重要的消費者是`fold`。他看起來像這樣:
~~~
let sum = (1..4).fold(0, |sum, x| sum + x);
~~~
`fold()`看起來像這樣:`fold(base, |accumulator, element| ...)`。它需要兩個參數:第一個參數叫做*基數*(*base*)。第二個是一個閉包,它自己也需要兩個參數:第一個叫做*累計數*(*accumulator*),第二個叫*元素*(*element*)。每次迭代,這個閉包都會被調用,返回值是下一次迭代的累計數。在我們的第一次迭代,基數是累計數。
好吧,這有點混亂。讓我們檢查一下這個迭代器中所有這些值:
| 基數 | 累計數 | 元素 | 閉包結果 |
|-----|-----|-----|-----|
| 0 | 0 | 1 | 1 |
| 0 | 1 | 2 | 3 |
| 0 | 3 | 3 | 6 |
我們可以使用這些參數調用`fold()`:
~~~
# (1..4)
.fold(0, |sum, x| sum + x);
~~~
那么,`0`是我們的基數,`sum`是累計數,`x`是元素。在第一次迭代,我們設置`sum`為`0`,然后`x`是`nums`的第一個元素,`1`。我們接著把`sum`和`x`相加,得到`0 + 1 = 1`。在我們第二次迭代,`sum`成為我們的累計值,元素是數組的第二個值,`2`,`1 + 2 = 3`,然后它就是最后一次迭代的累計數。在這次迭代中,`x`是最后的元素,`3`,那么`3 + 3 = 6`,就是我們和的最終值。`1 + 2 + 3 = 6`,這就是我們的結果。
(口哨)。最開始你見到`fold`的時候可能覺得有點奇怪,不過一旦你習慣了它,你就會在到處都用它。任何時候你有一個列表,然后你需要一個單一的結果,`fold`就是合適的。
消費者很重要還因為另一個我們沒有討論到的迭代器的屬性:惰性。讓我們更多的討論一下迭代器,你就知道為什么消費者重要了。
### 迭代器(Iterators)
正如我們之前說的,迭代器是一個我們可以重復調用它的`.next()`方法,然后它會給我們一個數據序列的結構。因為你需要調用函數,這意味著迭代器是*惰性的*(*lazy *)并且不需要預先生成所有的值。例如,下面的代碼并沒有真正的生成`1-99`這些數,而是創建了一個值來代表這個序列:
~~~
let nums = 1..100;
~~~
因為我們沒有用范圍做任何事,它并未生成序列。讓我們加上消費者:
~~~
let nums = (1..100).collect::<Vec<i32>>();
~~~
現在,`collect()`會要求范圍生成一些值,接著它會開始產生序列。
范圍是你會見到的兩個基本迭代器之一。另一個是`iter()`。`iter()`可以把一個向量轉換為一個簡單的按順序給出每個值的迭代器:
~~~
let nums = vec![1, 2, 3];
for num in nums.iter() {
println!("{}", num);
}
~~~
這兩個基本迭代器應該能勝任你的工作。還有一些高級迭代器,包括一個是無限的。
關于迭代器的介紹足夠了。迭代適配器是關于迭代器最后一個要介紹的內容了。讓我們開始吧!
### 迭代適配器(Iterator adapters)
*迭代適配器*(*Iterator adapters*)獲取一個迭代器然后按某種方法修改它,并產生一個新的迭代器。最簡單的是一個是`map`:
~~~
(1..100).map(|x| x + 1);
~~~
在其他迭代器上調用`map`,然后產生一個新的迭代器,它的每個元素引用被調用了作為參數的閉包。所以它會給我們`2-100`這些數字。好吧,看起來是這樣。如果你編譯這個例子,你會得到一個警告:
~~~
warning: unused result which must be used: iterator adaptors are lazy and
do nothing unless consumed, #[warn(unused_must_use)] on by default
(1..100).map(|x| x + 1);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~
又是惰性!那個閉包永遠也不會執行。這個例子也不會打印任何數字:
~~~
(1..100).map(|x| println!("{}", x));
~~~
如果你嘗試在一個迭代器上執行帶有副作用的閉包,不如直接使用`for`。
有大量有趣的迭代適配器。`take(n)`會返回一個源迭代器下`n`個元素的新迭代器,注意這對源迭代器沒有副作用。讓我們試試我們之前的無限迭代器,`count()`:
~~~
for i in (1..).take(5) {
println!("{}", i);
}
~~~
這會打印:
~~~
1
2
3
4
5
~~~
`filter()`是一個帶有一個閉包參數的適配器。這個閉包返回`true`或`false`。`filter()`返回的新迭代器只包含閉包返回`true`的元素:
~~~
for i in (1..100).filter(|&x| x % 2 == 0) {
println!("{}", i);
}
~~~
這會打印出1到100之間所有的偶數。(注意因為`filter`并不消費它迭代的元素,它傳遞每個元素的引用,所以過濾器使用`&x`來提取其中的整型數據。)
你可以鏈式的調用所有三種結構:以一個迭代器開始,適配幾次,然后處理結果。看看下面的:
~~~
(1..)
.filter(|&x| x % 2 == 0)
.filter(|&x| x % 3 == 0)
.take(5)
.collect::<Vec<i32>>();
~~~
這會給你一個包含`6`,`12`,`18`,`24`和`30`的向量。
這只是一個迭代器、迭代適配器和消費者如何幫助你的小嘗試。有很多非常實用的迭代器,當然你也可以編寫你自己的迭代器。迭代器提供了一個安全、高效的處理所有類型列表的方法。最開始它們顯得比較不尋常,不過如果你玩轉了它們,你就會上癮的。關于不同迭代器和消費者的列表,查看[迭代器模塊文檔](http://doc.rust-lang.org/std/iter/)。
- 前言
- 貢獻者
- 1.介紹
- 2.準備
- 3.學習 Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的 Rust
- 4.語法和語義
- 4.1.變量綁定
- 4.2.函數
- 4.3.原生類型
- 4.4.注釋
- 4.5.If語句
- 4.6.循環
- 4.7.所有權
- 4.8.引用和借用
- 4.9.生命周期
- 4.10.可變性
- 4.11.結構體
- 4.12.枚舉
- 4.13.匹配
- 4.14.模式
- 4.15.方法語法
- 4.16.Vectors
- 4.17.字符串
- 4.18.泛型
- 4.19.Traits
- 4.20.Drop
- 4.21.if let
- 4.22.trait 對象
- 4.23.閉包
- 4.24.通用函數調用語法
- 4.25.crate 和模塊
- 4.26.const和static
- 4.27.屬性
- 4.28.type別名
- 4.29.類型轉換
- 4.30.關聯類型
- 4.31.不定長類型
- 4.32.運算符和重載
- 4.33.Deref強制多態
- 4.34.宏
- 4.35.裸指針
- 4.36.不安全代碼
- 5.高效 Rust
- 5.1.棧和堆
- 5.2.測試
- 5.3.條件編譯
- 5.4.文檔
- 5.5.迭代器
- 5.6.并發
- 5.7.錯誤處理
- 5.8.選擇你的保證
- 5.9.外部函數接口
- 5.10.Borrow 和 AsRef
- 5.11.發布途徑
- 5.12.不使用標準庫
- 6.Rust 開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接進階
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 6.11.自定義內存分配器
- 7.詞匯表
- 8.語法索引
- 9.參考文獻
- 附錄:名詞中英文對照