C++11支持range-based for迴圈。這是一個很方便的特性,能省挺多代碼。以下代碼就能很方便的遍歷vector中的元素,並列印出來: 1 2 3 4 5 6 7 8 std::vector<int> int_vec; int_vec.push_back(1); int_vec.push_ ...
C++11支持range-based for迴圈。這是一個很方便的特性,能省挺多代碼。以下代碼就能很方便的遍歷vector中的元素,並列印出來:
1 2 3 4 5 6 7 8 |
std::vector< int > int_vec;
int_vec.push_back(1);
int_vec.push_back(2);
//如果要修改int_vec中的元素,將變數x聲明為 int& 即可
for ( int x: int_vec)
{
std::cout << x << endl;
}
|
可以遍歷的對象包括:
- 數組。(不包括指針)
- 定義了begin()和end()方法,且返回該方法返回迭代器的類對象。(STL 中所有容器都可以)
(對於動態生成的數組的遍歷,用下麵介紹的Range類也能省不少代碼)
參考http://en.cppreference.com/w/cpp/language/range-for 可知,
語句 for (
range_declaration :
range_expression)
loop_statement
與以下語句作用等價:
1 2 3 4 5 6 7 8 9 |
{
auto && __range = range_expression ;
for ( auto __begin = begin_expr,
__end = end_expr;
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
|
對於可遍歷的類對象,__begin和__end分別由類的begin()和end()方法產生。且由於__range變數是右值引用,如果range_expression的結果是右值,其將會在迴圈結束後析構。
這樣,C++11終於支持了這種現代編程語言都支持的遍歷方式了。但是,無論是語法還是標準庫都不支持對具體數字的遍歷,比如python中的 for i in xrange(1,5)語句中,x將連續取[1,4]中的值。(Boost庫有irange類可以滿足這個需求,但是下麵會討論下我的實現)
最直接的方法,就是寫一個函數,返回一個vector<int>對象,其元素為從begin到end的值。但這樣每次迴圈時都得構造一個這樣的對象,略慢。
從標準來看,如果一個類要支持這樣遍歷,至少得有begin()和end()方法。在for迴圈的初始化部分,調用了這兩個方法之後,就沒這個類啥事了——都是迭代器的事。所以很自然,從迭代器上下手。這個迭代器必須支持三種操作:!=,首碼++,解引用。有沒發現,如果這個“迭代器”是個int數值的話,上面迴圈中的__begin!=__end;++__begin語句就是一個非常自然的實現。現在的目標很簡單了:這個“迭代器”不遍歷容器中的每個元素,而就是一個簡單的int數值的封裝。對其解引用將返回這個數,而比較和自加操作均對這個數進行操作。
有想法之後,實現起來就很容易了。首先定義一個仿迭代器 FakeIter ,其對一個數值進行封裝,並重載必須的操作符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class FakeIter
{
typedef long _VType; //數值的類型為long。當然了,也可以寫個模板出來
public :
explicit FakeIter(_VType val)
:value_(val){}
bool operator != ( const FakeIter& other) const
{
return ( this ->GetValue()) != (other.GetValue());
}
_VType operator* () const
{
return GetValue();
}
const FakeIter& operator++ ()
{
++value_;
return * this ;
}
private :
_VType GetValue() const
{
return value_;
}
_VType value_;
};
|
至於“容器”類的實現,就更簡單了:實現begin()和end()方法,並返回上面的FakeIter就好了。類中的方法加了一些cout語句,可以更清楚的瞭解迴圈執行時具體方法的調用過程,實際用時可以刪掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Range
{
typedef long _VType; //同樣,也可以弄個模板出來,但是就不方便用了
public :
Range (_VType begin_v, _VType end_v)
:begin_value_(begin_v), end_value_(end_v)
{
cout<< "Range::Range()" <<endl;
}
~Range()
{
cout<< "Range::~Range()" <<endl;
}
FakeIter begin () const
{
cout<< "Range::begin()" <<endl;
return FakeIter(begin_value_);
}
FakeIter end () const
{
cout<< "Range::end()" <<endl;
return FakeIter(end_value_ );
}
private :
_VType begin_value_;
_VType end_value_;
};
|
好了,試試看這貨有沒用:
1 2 3 4 5 |
for ( auto x: Range(1,5))
{
std::cout<<x<<endl;
}
std::cout<< "Loop end" <<endl;
|
在vs2012及clang下輸出如下:
Range::Range()
Range::begin()
Range::end()
1
2
3
4
Range::~Range()
Loop end
嗯,如果把那些影響視線的輸出語句的註釋掉的話貌似能用了。現在要遍歷一個new生成的數組的話,只需用這東西Range個下標,世界就清靜了。
但是,步長呢!好吧,貌似我暫時還沒這方面的需求。不過實現起來也很簡單:修改FakeIter類就可以了,可以增加一個表示步長的成員,然後再修改自加操作。更進一步,也可以加一個生成器方法,就能變成一個更通用的生成器了。配合C++11 lambda操作符,用起來也挺方便。