1、 for循環的新用法
在C++98/03中,通過for循環對一個容器進行遍歷,一般有兩種方法,常規的for循環,或者使用<algorithm>中的for_each方法。
for循環遍歷:
void func(void) { std::vector<int> arr; for(auto it = arr.begin(); it != arr.end(); ++it) { std::cout << *it << std::endl; } }
for_each方法:
void vFuncCall(int n) { std::cout << n << std::endl; } void func2(void) { std::vector<int> arr; std::for_each(arr.begin(), arr.end(), vFuncCall); }
for_each相比一般的for循環,只需關注容器元素的類型,但是都是基於范圍的循環,必須顯示的給出容器的開始(begin)和結束(end)。C++11中改善了這種遍歷方式,不再需要給出容器的兩端,循環會自動根據容器的范圍自動的展開,在循環中屏蔽了迭代器的遍歷細節,直接抽取容器中的元素進行運算。我們來看C++11中是怎么遍歷容器的:
void func(void) { std::vector<int> arr; for(auto n : arr) { std::cout << n << std::endl; } }
是不是很簡潔,n表示arr中的一個元素, auto會被編譯器自動推導出類型,此例中被推導為vector中的int類型。當然,也可以直接寫出類型:
std::vector<int> arr; for(int n : arr)
同時,這種循環中,冒號前面的變量支持隱式轉換的,因此在使用時需要注意:
std::vector<int> arr; for(char c : arr) //int會被隱式轉換為char
在這種循環中,我們都是只讀方式來遍歷容器的,如果需要改變容器中的值,我們需要加上引用,如果是只希望遍歷而不是修改,我們可以使用const auto & 來定義n的類型,這樣對於復制負擔比較大的容器元素(std::vector<std::string>數組等)也可以無耗的進行遍歷。
std::vector<int> arr; for(auto & n : arr) { std::cout << n++ << std::endl; //打印,並把元素的值+1 }
2、 使用細節
2.1 推導類型
我們來看使用范圍的for循環和普通的for循環有什么區別:
std::map<std::string, int> mmsi_test = {{"1", 1}, {"2", 2}, {"3", 3}}; //一般情況的for循環 for(auto it = mmsi_test.begin(); it != mmsi_test.end(); ++it) { std::cout << it->first << "->" << it->second << std::endl; } //基於范圍的for循環 for(auto val : mmsi_test) { std::cout << val.first << "->" << val.second << std::endl; }
可以看出:
- for循環中的val類型是std::pair,對於map這種關聯性容器來說,需要使用val.first或者val.second來提取鍵值;
- auto自動推導出來容器元素的類型是value_type,而不是迭代器。
2.2 容器約束
如果我們要改變某些容器元素的值,通過auto &可以解決大多數問題,但是某些特殊容器並不能達到我們預期的結果。比如我們希望在循環中對set的值進行修改,但是set的內部元素的值是可讀的----由set容器的特性決定的,因此for循環中的auto &會被推導為const xx &。同樣基於范圍的map遍歷一樣,for循環得到的std::pair引用,是不能修改first的。
void func(void) { std::set<int> ss = {1, 2, 3}; for(auto &val : ss) { //error increment of read-only refrence 'val' std::cout << val++ << std::endl; } }
2.3訪問頻率
我們先來看一段代碼,測試一下C++11中的循環對於容器的訪問頻率。
#include <iostream> #include <vector> std::vector<int> g_arr = {1,2,3,4,5}; std::vector<int>& func(void) { std::cout << "get range->" << std::endl; return g_arr; } int main(void) { for(auto val : func()) { std::cout << val << std::endl; } return 0; }
程序的執行結果:
我們可以從結果中看出,不論基於范圍的for循環迭代了多少次,func()只在第一次迭代之前被調用,在循環之前就確定好迭代的范圍,而不是在每次迭代之前都去調用一次end()。所以可以得出結論:基於范圍的for循環,冒號后面的表達式只會被執行一次。
那么我們來看如果在基於范圍的for循環中修改容器會出現什么情況:
#include <iostream> #include <vector> int main(void) { std::vector<int> arr = {1,2,3,4,5}; for(auto val : arr) { std::cout << val << std::endl; arr.push_back(100); //擴大容器 } return 0; }
在centos6.7 64位系統運行結果:
從結果看出,這並不是我們需要的結果,如果把vector換成list,結果又不一樣。
因為介於范圍的for循環其實是普通for循環的語法糖,同普通的循環一樣,在迭代時修改容器可能引起迭代器失效,導致一些意料之外的結果。由於我們這里是沒法看到迭代器的,所以在基於范圍的for循環中修改容器到底會造成什么樣的影響非常困難。