反向迭代器(Reverse Iterator)是普通迭代器的適配器,通過重新定義自增和自減操作,以達到按反序遍歷元素的目的。如果在標准算法庫中用反向迭代器來代替普通的迭代器,那么運行結果與正常情況下相反。除此之外,其用法與普通迭代器完全一樣,我們不作詳細討論。
反向迭代器reverse_iterator是一種反向遍歷容器的迭代器,也就是從最后一個元素到第一個元素遍歷容器。反向迭代器的自增(或自減)的含義反過來了:對於反向迭代器,++運算符將訪問前一個元素,–運算符將訪問下一個元素。
反向迭代器與迭代器的轉換
reverse_iterator與iterator都繼承自_Iterator_base,它們是可以相互轉換的。
-
調用reverse_iterator的base()方法可以獲取"對應的"iterator。
-
可以用iterator構造一個"對應的"reverse_iterator。
下面的兩個例子展示了它們之間的轉換:
list<int> test_list;
for (size_t i = 1; i < 8; i++)
{
test_list.push_back( i*2 );
}
list<int>::reverse_iterator rit = find(test_list.rbegin(), test_list.rend(), 8);
list<int>::iterator it(rit.base());
cout << *rit << endl;
cout << *it << endl;
上面的代碼是先查找到一個指向8的reverse_iterator,並reverse_iterator的base()方法得到一個iterator。但是從輸出上看,iterator指向的元素的值並不是8,而是10。
list<int> test_list;
for (size_t i = 1; i < 8; i++)
{
test_list.push_back( i*2 );
}
list<int>::iterator it = find(test_list.begin(), test_list.end(), 8);
list<int>::reverse_iterator rit(it);
cout << *it << endl;
cout << *rit << endl;
上面的代碼是先查找一個指向8的iterator,然后用該iterator構造一個reverse_iterator。但是從輸出上看,reverse_iterator指向的元素並不是8,而是6。
接下來主要討論的是反向迭代器的一個很特殊、也很容易出錯的性質,即它的“邏輯位置”與“物理位置”。先通過看一個例子開始:
- vector<int> vec;
- for(vector<int>::size_type i=1; i<10; ++i)
- {
- vec.push_back(i);
- }
- vector<int>::iterator itr = vec.begin()+4;
- cout<<*itr<<endl;
- vector<int>::reverse_iterator r_itr(itr);
- cout<<*r_itr<<endl;
這個例子中,vec中存放從1到9的9個連續數字,並初始化一個普通迭代器指向數字5,打印輸出結果顯而易見,為5。然后再初始化一個反向迭代器,該迭代器指向與普通迭代器一樣的物理位置,然后打印輸出,這時結果為多少呢?
如果你不了解反向迭代器這個特殊的性質的話,很容易誤認為結果一樣是5。但實際情況不是這樣,而是4,即前一個位置處的元素!具體原因,即涉及到反向迭代器的“物理位置”與“邏輯位置”兩個概念。
我們都知道,一個容器的范圍用普通迭代器表示為一個“半開半閉”的區間。頭部為begin,指向容器第一個元素的位置。末尾為end,指向最后一個元素的下一個位置,每個容器都提供了這樣一個位置,盡管該位置不可引用,但卻是個合法的地址。相反,第一個元素位置的前一個位置容器卻沒有任何保證,比如對於vector和string來說,就是非法的位置。這里我們說“合法”與“非法”,簡單來講,可以這樣認為,一個合法的位置對於迭代器來說是可以達到的,像最后一個元素的下一個位置end()。而對於首元素的前一個位置,迭代器是無法指向它的,begin()-1這個表達式會導致異常。因此,反向迭代器與普通迭代器在物理位置上保持了一一對應,即rbegin()對應普通迭代器的end()位置,rend()對應其begin()位置。
但是,為了讓反向迭代器與普通迭代器在概念上保持一致性,即begin()(反向迭代器對應為rbegin())對應第一個元素(對於反向迭代器來說,最后一個元素即第一個元素),end()(反向迭代器對應為rend())對應最后一個元素的下一個位置,於是標准庫的設計者們想出這樣一個方法,即反向迭代器的邏輯位置等於其物理位置的前一個位置。換句話說,物理位置對應迭代器在內存中的實際位置,邏輯位置對應迭代器對應容器中元素的位置。這樣,對於rbegin()來說,它物理位置是容器最后一個元素的下一個位置,邏輯位置即容器最后一個元素的位置(對反向迭代器來說就是第一個元素元素的位置),同理rend()物理位置為容器第一個元素位置,邏輯位置即第一個位置的前一個位置(依然不可解引用)。這樣,反向迭代器與普通迭代器便有了一致的概念,即“半開半閉”區間。更為直觀的演示如下圖:
(該圖片來自:《The C++ Standard Library, A Tutorial And Reference》)
這樣,開頭那個例子就很容易解釋了,反向迭代器被初始化為與前一個普通迭代器一樣的物理位置(對應元素5),其邏輯位置即前一個位置,因些通過解引用得到元素4.
反向迭代器的操作
了解了反向迭代器與迭代器的關系,再來對容器進行操作就很簡單了。下面是常用操作的例子,例子中用的test_list的內容如上圖所示:
1. 遍歷容器
for(list<int>::reverse_iterator rit = test_list.rbegin(); rit != test_list.rend(); ++rit)
{
cout<< *rit << " ";
}
2. 插入元素
假設要在rit指向的位置插入一個新元素150,那么150應該在8和10之間。對於rit而言,150應該插入到rit的前一個位置。對於it (it = rit.base()),150應該插入it的前一個位置。
所以,要實現在一個reverse_iterator rit指出的位置上插入新元素,在rit.base()指向的位置插入就行了。
操作代碼如下:
list<int>::reverse_iterator rit = find(test_list.rbegin(), test_list.rend(), 8);
test_list.insert(rit.base(), 150);
3. 刪除元素
假設要刪除rit指向的元素,就不直能刪除rit.base()指向的元素了,rit指向的是8,而rit.base()指向的是10。實際上我們要刪除的是rit.base()的前一個元素。
所以,要實現在一個reverse_iterator rit指出的位置上刪除元素,那么刪除rit.base()的前一個元素就行了。
操作代碼如下:
for(list<int>::reverse_iterator rit = test_list.rbegin(); rit != test_list.rend();)
{
if (8 == *rit)
{
list<int>::iterator it = –rit.base() ; // 用(++rit).base()也可;
list<int>::iterator it_after_del = test_list.erase(it);
rit = list<int>::reverse_iterator(it_after_del);
}
else
{
++rit;
}
}
調用erase()刪除元素后,返回值是指向被刪除的元素的下一個元素的iterator,所以還需要把返回的iterator變成reverse_iterator賦值給rit。
當然,上面的代碼寫成這樣也是一個意思:
for(list<int>::reverse_iterator rit = test_list.rbegin(); rit != test_list.rend();)
{
if (8 == *rit)
{
rit = list<int>::reverse_iterator(test_list.erase(–rit.base()));
}
else
{
++rit;
}
}