反向迭代器reverse_iterator與正向迭代器iterator之間的轉換(以及例項應用)
反向迭代器
相信大家對正向迭代器應該都很熟悉,然而對於反向迭代器的使用確是有幾處需要注意的地方,在此記錄一下。先看STL原始碼處註釋如下:
/** * Bidirectional and random access iterators have corresponding reverse * %iterator adaptors that iterate through the data structure in the * opposite direction. They have the same signatures as the corresponding * iterators. The fundamental relation between a reverse %iterator and its * corresponding %iterator @c i is established by the identity: * @code * &*(reverse_iterator(i)) == &*(i - 1) * @endcode * * <em>This mapping is dictated by the fact that while there is always a * pointer past the end of an array, there might not be a valid pointer * before the beginning of an array.</em> [24.4.1]/1,2 * * Reverse iterators can be tricky and surprising at first. Their * semantics make sense, however, and the trickiness is a side effect of * the requirement that the iterators must be safe. */
也即兩者相差一個元素,從一個反向迭代器獲得對應的正向迭代器需要使用 base() 方法,如下圖應該可以清楚闡明兩者的關係
即:如果 ri 為指向元素 3 的反向迭代器,則呼叫 base() 方法 i = ri.base() 得到的將是指向元素 4 的正向迭代器 i 。
vector<int> vec = {1, 2, 3, 4, 5, 6}; vector<int>::reverse_iterator rfirst = reverse_iterator<vector<int>::iterator>(vec.end()); vector<int>::reverse_iterator rlast = reverse_iterator<vector<int>::iterator>(vec.begin()); vector<int>::reverse_iterator rsecond = next(rfirst); cout << *rfirst << endl; cout << *rsecond << endl; cout << *(rsecond.base()) << endl;
得到的輸出如下
6 5 6
反向迭代器刪除元素
所有的 erase 函式都只接受正向迭代器 iterator,所以如果想在反向遍歷刪除元素時必須要將 reverse_iterator 轉換為 iterator 後再執行 erase 操作,所以反向迭代器與正向迭代器相差 1 的這個細節在這裡必須要考慮清楚。比如按上圖 ri 指向元素 3,但轉換為正向迭代器後實際為指向元素 4 的位置,為了能正確執行刪除,必須先將反向迭代器前進 1 步再轉為正向迭代器。
vector<int> vec = {1, 2, 3, 4, 5, 6}; for(auto rit = vec.rbegin(); rit != vec.rend();){ if(*rit % 2 == 0){ vec.erase((++rit).base()); }else{ ++rit; } } for(auto& x : vec) cout << x << " ";
輸出結果為
1 3 5
對於逆向迭代器,很重要的一點是需要弄清楚邏輯位置和實際位置二者的區別。
下圖顯示了逆向迭代器的位置和所指的數值:
可以發現,逆向迭代器所指位置(實際位置)和所代表的的數值(邏輯位置或數值)是不同的。C++這麼做是有其原因的。導致這個行為的原因是區間的半開性。為了能夠制定容器內的所有元素,我們必須運用最後一個元素的下一個位置。但是對於reverse迭代器而言,這個位置位於第一個元素之前。這時候問題就出現了,這個位置也許並不存在,因為容器並不要求其第一個元素之前的位置合法。
因此,逆向迭代器運用了一個小技巧:實際上倒置了“半開原則”,即逆向迭代器所定義的區間不包括起點,而包括終點。但是邏輯上一如常態。這樣就導致了逆向迭代器實際所指的元素位置和邏輯上所指的元素位置就不一致。 下面再看看將一個迭代器轉化為逆向迭代器的過程:
可以發現,迭代器的實際位置(元素)不變,但是邏輯位置(元素)發生了變化。圖中pos迭代器轉化為逆向迭代器rpos後實際位置還是5,但是邏輯位置是4.即邏輯元素位置是實際位置的前一個位置。測試程式碼:
#include
#include
#include
using namespace std;
int main()
{ vector coll;
//insert elements from 1 to 9
for (int i=1; i::iterator pos; pos = find (coll.begin(), coll.end(), 5);
//print value to which iterator pos refers
cout << "pos: " << *pos << endl;
//print value to which iterator pos refers
cout << "pos: " << *pos << endl;
//convert iterator to reverse iterator rpos
vector<int>::reverse_iterator rpos(pos);
//print value to which reverse iterator rpos refers
cout << "rpos: " << *rpos <<endl;
}
例項:
有經驗的程式設計師都知道,list是連結串列,可以遍歷刪除,刪除的過程類似於以下程式碼(遍歷刪除521):
int key=521;
list<int>::iterator it=l.begin();//l為std::list<int>
for (; it != l.end();)
{
if (key== *it) {
it = l.erase(it); //自動返回下一個元素的地址,不用再主動前移指標
}
else {
++it;
}
}
這是我們通常使用的刪除方法,我們現在有一種場景:
公司為了懲罰員工遲到(我瞎說的,遲到了是要扣工資的。工作實際遇到的場景描述過於複雜),對於某一天的遲到的員工進行處罰,並且,由於有些員工是,年齡大了,網開一面,就不懲罰了。
假設我們的員工開啟記錄都存放在一個list裡面,按照時間順序存放,來的晚的打卡記錄就在後面。
定義打卡的結構體:
struct DingDong
{
long id;//員工id
long time;//打卡時間
int age;//員工年齡,人性化考慮,大於45歲的員工不處罰
};
資訊定義:
typedef std::list<DingDong> DingDongList;
DingDongList dd_list;///全部員工開啟記錄
DingDongList punish_list;//懲罰員工名單
long normal_time = 90000;//正常上班時間
int normal_avg = 45;//懲罰的年齡上限,包含45歲
員工的打卡記錄類似於:
場景描述完畢,我們開始處理
我們是個大廠子,有個10萬的員工(我們不是鵝廠的,場景純屬虛構),
每天遲到的可能就100到200,從頭開始遍歷,很不明智,於是我們逆序遍歷,並且,90000上班(9:00:00),大於45歲不處罰,程式碼如下
DingDongList::reverse_iterator r_iter = dd_list.rbegin();
for (; r_iter != dd_list.rend();)
{
if (r_iter->time <= normal_time) break;//遲到的員工都找到了
if (r_iter->age<=45)
{
punish_list.push_back(*r_iter);
//刪掉該節點
}
else
{
++r_iter;
}
}
//punish_list拿去懲罰吧,dd_list剩下的員工都是好同志(45歲以上遲到的老人也算是好同志)。
看到了“刪除該節點"位置,該怎麼刪掉該節點呢。通過查詢,我們發現list的erase()方法的引數只有如下幾種,並沒有對反向迭代器的刪除操作:
那怎麼辦呢?難道就無法刪除反向迭代器的元素了?
通過分析STL的iterator和reverse_iterator原始碼實現,發現了一些東西:
首先是,反向迭代器是繼承迭代器的
反向迭代器的求值操作求的是 (*(--current)),即當前儲存迭代器的前一個迭代器的值。
->操作則是求*操作後,求地址。
這些事實說明,reverse_iterator的求值和->操作都返回的是--iterator位置的資料。或者說,當前反向迭代器裡面的指標指向的是,所訪問的元素的下一個元素。
怎麼把reverse_iterator轉換成iterator呢?畢竟erase()能夠刪除的是iterator。
經過查證,發現了
那麼,base()方法求到的是
實際上我們要刪除的是元素5,那麼求出指向5的正向迭代器呢?
分析程式碼我們知道,反向迭代器的++操作是指標往前挪動,即++rbegin()之後,reverse_iterator內部儲存的指標就指向了元素5,然後求一下base()就是指向5的正向迭代器了(++rbegin()).base();
那麼,逆序刪除list元素的過程就呼之欲出了吧,放程式碼:
DingDongList::reverse_iterator r_iter = dd_list.rbegin();
for (; r_iter != dd_list.rend();)
{
if (r_iter->time <= normal_time) break;//遲到的員工都找到了
if (r_iter->age <= 45)
{
punish_list.push_back(*r_iter);
r_iter = DingDongList::reverse_iterator(dd_list.erase((++r_iter).base()));
}
else
{
++r_iter;
}
}
到此,利用反向迭代器刪除某些元素的功能就完成了。遲到的年輕員工,等著扣工資吧