一. 基於范圍的for循環簡介
在C++03/98中,不同的容器和數組,遍歷的方法不盡相同,寫法不統一,也不夠簡潔,而C++11基於范圍的for循環以統一,簡潔的方式來遍歷容器和數組,用起來更方便了。
數組循環:
1 using namespace std; 2 3 const int size = 5; 4 int* p = new int[size]{1,2,3,4,5}; 5 for(int i =0;i<size;i++){ 6 cout<<p[i]<<" "; 7 }
容器循環:
1 using namespace std; 2 3 vector<int> vec; 4 for (auto it=vec.begin(),it!=vec.end();it++){ 5 cout<<*it<<" "; 6 }
當然,<algorithm>中還有一個for_each算法可以用來對容器進行遍歷
Function for_each( InputIterator begin, InputIterator end, Function f ) { while ( begin != end ) f( *begin++ ); }
1 using namespace std; 2 3 void do_cout(int& num){ 4 cout<<num<<" "; 5 } 6 7 vector<int> vec; 8 for_each(vec.begin(),vec.end(),do_cout);
for_each
優點:不再需要關注迭代器(Iterator)的概念
缺點:必須顯示的給出容器的開頭(Begin)和結尾(End)
在C++11中終於有基於范圍的for循環(The range-based for statement)。
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 int main(){ 7 vector<int> arr = {1, 2, 3}; 8 for(auto n : arr){ 9 cout << n << endl; 10 } 11 12 return 0; 13 }
在上面的基於范圍的for循環中,在n的定義之後,緊跟一個冒號(:),之後直接寫上需要遍歷的表達式,for循環將自動以表達式返回的容器為范圍進行迭代
需要注意
1.在上面的例子中,我們都是在使用只讀方式遍歷容器。如果需要在遍歷時修改容器中的值,則需要如下使用引用。
1 for ( auto& n : arr){ 2 cout<< n++ << endl; 3 }
2.若只是希望遍歷,而不希望修改,可以使用const auto&來定義n的類型。這樣對於復制負擔比較大的容器元素(比如一個std::vector<std::string>數組)也可以無損耗地進行遍歷。
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 5 using namespace std; 6 7 int main(){ 8 vector<string> arr={"li ming","wang lei","han meimei"}; 9 for(const auto& n : arr){ 10 cout<<arr<<endl; 11 } 12 return 0; 13 }
二.基於范圍的for循環的使用細節
1.
1 #include <iostream> 2 #include <map> 3 4 using namespace std; 5 6 int main(){ 7 map<string,int> ma={ 8 {"1",1},{"2",2},{"3",3} 9 }; 10 for(auto & val : mm){ 11 cout<< val.first <<"->" << val.second <<endl; 12 } 13 return 0; 14 }
1).for循環中val的類型是std::pair.因此,對於map這種關聯性容器而言,需要使用val.first或val.second來提取鍵值。
2).auto自動推導師出的類型是容器中的value_type,而不是迭代器。
2.
1 #include <iostream> 2 #include <set> 3 4 using namespace std; 5 6 int main(){ 7 set<int> se = {1 , 2, 3}; 8 for (auto& val : ss){ 9 //error:increment of read-only reference 'val' 10 cout<< val++ << Lendl; 11 } 12 return 0; 13 }
在使用基於范圍的for循環時,還需要注意容器本身的一些約束。
1).例子中,auto & 定義了std::set<int>中元素的引用,希望能夠在循環中對set的值進行修改,但std::set的內部元素是只讀的,因此,for循環中的auto& 會被推導為const int&。
2).在std::map的遍歷中,基於范圍白for循環中的std::pair引用,是不能夠修改first的。
3.
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 vector<int> arr = { 1, 2, 3, 4, 5}; 7 8 vector<int>& get_range(){ 9 cout<<"get_range ->: "<< endl; 10 return arr; 11 } 12 13 int main(){ 14 for(auto val : get_range()){ 15 cout<< val << endl; 16 } 17 return 0; 18 }
輸出結果:
get_range ->:
1
2
3
4
5
1).從上面的例子可以看到,無論基於范圍的for循環迭代了多少次,冒號後面的表達式只會執行一次,只會在第一次迭代之前調用。
4.基於范圍的for循環等價的普通的for循環如下:
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 int main(){ 7 vector<int> arr = {1, 2, 3, 4, 5}; 8 9 auto && __range = (arr); 10 for( auto __begin = __range.begin(), __end = __range.end();__begin!=__end;++__begin(){ 11 auto val = *__begin; 12 cout<< val <<endl; 13 arr.push_back(0); 14 } 15 return 0; 16 }
1).基於范圍的for循環其實是普通for循環的語法糖,從上面的代碼可以清晰地看到,和我們平時寫的遍歷容器不同,基於范圍的for循環傾向於在循環開始之前確定好迭代的范圍,而不是在每次迭代之前都去調用一次arr.end()。
三.讓基於范圍的for循環支持自定義類型
在之前提及的vector,set,map,都是標准模板庫中的容器。都實現了begin,end等函數。那麼對於我們自己定義的容器類,如何才能讓它支持range-based for呢?下面是書中的一個樣例。
1 //迭代器類的實現 2 3 namespace detail_range{ 4 5 template<typename T> 6 class iterator 7 { 8 public: 9 using value_type = T; 10 using size_type = size_t; 11 12 iterator(size_type cur_start, value_type begin_val, value_type step_val) 13 :cursor_(cur_start),step_(step_val),value_(begin_val){ 14 value_ += (step_ * cursor_); 15 } 16 17 value_type operator*() const{ 18 return value_; 19 } 20 21 bool operator!=(const iterator& hrs) const{ 22 return (cursor_ != rhs.cursor_); 23 } 24 25 iterator& operator++(void) //prefix ++ operator only 26 value_ += step_; 27 ++ cursor_; 28 return (*this); 29 } 30 31 private: 32 size_type cursor_; 33 const value_type step_; 34 value_type value_; 35 }; 36 37 }//namespace detail_range
C++11,定義模板的別名只能使用using。定義一般類型的別名時與typedef沒有區別。
//impl為要實現的類似容器的類,我們給它定義了容器所要擁有的基本的概念抽象 namespace detail_range{ template<typename T> class imply { public: using value_type = T; using reference = const value_type&; using const_reference = const value_type&; using iterator = const detail_range::iterator<value_type>; using const_iterator = const detail_range::iterator<value_type>; using size_type = typename iterator::size_type; impl(value_type begin_val, value_type end_val, value_type step_val) : begin_(begin_val),end_(end_val),step_(step_val),max_count_(get_adjusted_count()){} size_type size(void) const{ return max_count_; } const_iterator begin(void) const{ return { 0, begin_, step_ }; } const_iterator end(void) const{ return {max_count_ , begin_, step_ }; } private: const value_type begin_; const value_type end_; const value_type step_; const size_type max_count_; size_type get_adjusted_count(void) const{ if(step_ > 0 && begin_ >= end_) throw std::logic_error("End value must be greater than begin value."); else if(step_ < 0 && begin_ <= end_) throw std::logic_error("End value must be less than begin value."); size_type x = static_cast<size_type>((end_ - begin_)/step_); if( begin_+ (step_ * x) != end_) ++ x; return x; } }; }//namespace detail_range
max_count_為最大迭代次數,通過調用get_adjusted_count函數獲得,他會先判斷begin_,end_和step_的合法性。
1 template<typename T> 2 detail_range::imply<T> range(T end){ 3 return { {}, end, 1 };//使用初始化列表 4 } 5 6 template<typename T> 7 detail_range::impl<T> range(T begin, T end){ 8 return { begin, end, 1}; 9 } 10 11 template <typename T, typename U> 12 auto range(T begin, T end, U step)->detail_range::impl<decltype(begin+step)>{ 13 using r_t = detail_range::impl<decltype(begin + step)>; 14 return r_t(begin, end, step); 15 }
1 #include <iostream> 2 3 using namespace std; 4 5 int main(){ 6 cout << "range(15):"; 7 for(int i : range(15)){ 8 cout << " " << i; 9 } 10 cout<<endl; 11 12 cout << "range(2,6):"; 13 for( auto i : range(2, 6)){ 14 cout << " " << i; 15 } 16 cout<<endl; 17 18 const int x = 2, y = 6, z = 3; 19 cout << "range(2,6,3):"; 20 for (auto i : range(x,y,z)){ 21 cout << " " << i; 22 } 23 cout<<endl; 24 25 cout << "range(-2,-6,-3):"; 26 for (auto i : range(-2,-6,-3)){ 27 cout << " " << i; 28 } 29 cout<<endl; 30 31 cout << "range(10.5,10.5):"; 32 for (auto i : range(10.5,10.5)){ 33 cout << " " << i; 34 } 35 cout<<endl; 36 37 cout << "range(35,27,-1):"; 38 for (auto i : range(35,27,-1)){ 39 cout << " " << i; 40 } 41 cout<<endl; 42 43 cout << "range(2,8,0.5):"; 44 for (auto i : range(2,8,0.5)){ 45 cout << " " << i; 46 } 47 cout<<endl; 48 49 cout << "range(8,7,-0.1):"; 50 for (auto i : range(8,7,-0.1)){ 51 cout << " " << i; 52 } 53 cout<<endl; 54 55 cout << "range('a','z'):"; 56 for (auto i : range('a','z')){ 57 cout << " " << i; 58 } 59 cout<<endl; 60 61 return 0; 62 } 63 64 //示例運行的結果如下: 65 //range(15): 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 66 //range(2, 6): 2 3 4 5 67 //range(2, 6, 3): 2 5 68 //range(-2, -6, -3): -2 -5 69 //range(10.5, 10.5): 10.5 11.5 12.5 13.5 14.5 70 //range(35, 27, -1): 35 34 33 32 31 30 29 28 71 //range(2, 8, 0.5): 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 72 //range(8, 7, -0.1): 8 7.9 7.8 7.7 7.6 7.5 7.4 7.3 7.2 7.1 73 //range('a', 'z'): a b c d e f g h i j k l m n o p q r s t u v w x y