轉載來自:https://subingwen.cn/cpp/for/
在 C++98/03 中,不同的容器和數組遍歷的方式不盡相同,寫法不統一,也不夠簡潔,而 C++11 基於範圍的 for 循環可以以簡潔、統一的方式來遍歷容器和數組,用起來也更方便了。
1. for 循環新語法
在介紹新語法之前,先來看一個使用迭代器遍歷容器的例子:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> t{ 1,2,3,4,5,6 }; for (auto it = t.begin(); it != t.end(); ++it) { cout << *it << " "; } cout << endl; return 0; }
我們在遍歷的過程中需要給出容器的兩端:開頭(begin)和結尾(end),因爲這種遍歷方式不是基於範圍來設計的。在基於範圍的for循環中,不需要再傳遞容器的兩端,循環會自動以容器爲範圍展開,並且循環中也屏蔽掉了迭代器的遍歷細節,直接抽取容器中的元素進行運算,使用這種方式進行循環遍歷會讓編碼和維護變得更加簡便。
C++98/03 中普通的 for 循環,語法格式:
for (表達式 1; 表達式 2; 表達式 3) { // 循環體 }
C++11 基於範圍的 for 循環,語法格式:
for (declaration : expression) { // 循環體 }
在上面的語法格式中 declaration 表示遍歷聲明,在遍歷過程中,當前被遍歷到的元素會被存儲到聲明的變量中。expression 是要遍歷的對象,它可以是表達式、容器、數組、初始化列表等。
使用基於範圍的 for 循環遍歷容器,示例代碼如下:
#include <iostream> #include <vector> using namespace std; int main(void) { vector<int> t{ 1,2,3,4,5,6 }; for (auto value : t) { cout << value << " "; } cout << endl; return 0; }
在上面的例子中,是將容器中遍歷的當前元素拷貝到了聲明的變量 value 中,因此無法對容器中的元素進行寫操作,如果需要在遍歷過程中修改元素的值,需要使用引用。
#include <iostream> #include <vector> using namespace std; int main(void) { vector<int> t{ 1,2,3,4,5,6 }; cout << "遍歷修改之前的容器: "; for (auto& value : t) { cout << value++ << " "; } cout << endl << "遍歷修改之後的容器: "; for (auto& value : t) { cout << value << " "; } cout << endl; return 0; }
代碼輸出的結果:
遍歷修改之前的容器: 1 2 3 4 5 6 遍歷修改之後的容器: 2 3 4 5 6 7
對容器的遍歷過程中,如果只是讀數據,不允許修改元素的值,可以使用 const 定義保存元素數據的變量,在定義的時候建議使用 const auto &,這樣相對於 const auto 效率要更高一些。
#include <iostream> #include <vector> using namespace std; int main(void) { vector<int> t{ 1,2,3,4,5,6 }; for (const auto& value : t) { cout << value << " "; } return 0; }
2. 使用細節
2.1 關係型容器
使用基於範圍的 for 循環有一些需要注意的細節,先來看一下對關係型容器 map 的遍歷:
#include <iostream> #include <string> #include <map> using namespace std; int main(void) { map<int, string> m{ {1, "lucy"},{2, "lily"},{3, "tom"} }; // 基於範圍的for循環方式 for (auto& it : m) { cout << "id: " << it.first << ", name: " << it.second << endl; } // 普通的for循環方式 for (auto it = m.begin(); it != m.end(); ++it) { cout << "id: " << it->first << ", name: " << it->second << endl; } return 0; }
在上面的例子中使用兩種方式對 map 進行了遍歷,通過對比有兩點需要注意的事項:
使用普通的 for 循環方式(基於迭代器)遍歷關聯性容器, auto 自動推導出的是一個迭代器類型,需要使用迭代器的方式取出元素中的鍵值對(和指針的操作方法相同):
it->first
it->second
使用基於訪問的 for 循環遍歷關聯性容器,auto 自動推導出的類型是容器中的 value_type,相當於一個對組(std::pair)對象,提取鍵值對的方式如下:
it.first
it.second
2.2 元素只讀
通過對基於範圍的 for 循環語法的介紹可以得知,在 for 循環內部聲明一個變量的引用就可以修改遍歷的表達式中的元素的值,但是這並不適用於所有的情況,對應 set 容器來說,內部元素都是隻讀的,這是由容器的特性決定的,因此在 for 循環中 auto & 會被視爲 const auto & 。
#include <iostream> #include <set> using namespace std; int main(void) { set<int> st{ 1,2,3,4,5,6 }; for (auto& item : st) { cout << item++ << endl; // error, 不能給常量賦值 } return 0; }
除此之外,在遍歷關聯型容器時也會出現同樣的問題,基於範圍的for循環中,雖然可以得到一個std::pair引用,但是我們是不能修改裏邊的first值的,也就是key值。
#include <iostream> #include <string> #include <map> using namespace std; int main(void) { map<int, string> m{ {1, "lucy"},{2, "lily"},{3, "tom"} }; for (auto& item : m) { // item.first 是一個常量 error cout << "id: " << item.first++ << ", name: " << item.second << endl; // error } return 0; }
2.3 訪問次數
基於範圍的 for 循環遍歷的對象可以是一個表達式或者容器 / 數組等。假設我們對一個容器進行遍歷,在遍歷過程中 for 循環對這個容器的訪問頻率是一次還是多次呢?我們通過下面的例子驗證一下:
#include <iostream> #include <vector> using namespace std; vector<int> v{ 1,2,3,4,5,6 }; vector<int>& getRange() { cout << "get vector range..." << endl; return v; } int main(void) { for (auto val : getRange()) { cout << val << " "; } cout << endl; return 0; }
輸出的結果如下:
get vector range... 1 2 3 4 5 6
從上面的結果中可以看到,不論基於範圍的 for 循環迭代了多少次,函數 getRange () 只在第一次迭代之前被調用,得到這個容器對象之後就不會再去重新獲取這個對象了。
結論:
對應基於範圍的 for 循環來說,冒號後邊的表達式只會被執行一次。在得到遍歷對象之後會先確定好迭代的範圍,基於這個範圍直接進行遍歷。如果是普通的 for 循環,在每次迭代的時候都需要判斷是否已經到了結束邊界。