反向迭代器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
   *
   *  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. [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() 方法,如下图应该可以清楚阐明两者的关系

                                                  反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第1张图片

即:如果 ri 为指向元素 3 的反向迭代器,则调用 base() 方法 i = ri.base() 得到的将是指向元素 4 的正向迭代器 i 。

 

    vector vec = {1, 2, 3, 4, 5, 6};

    vector::reverse_iterator rfirst = reverse_iterator::iterator>(vec.end());
    vector::reverse_iterator rlast = reverse_iterator::iterator>(vec.begin());
    vector::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 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::reverse_iterator rpos(pos);

  //print value to which reverse iterator rpos refers

  cout << "rpos: " << *rpos <

   }

 

实例:

 

有经验的程序员都知道,list是链表,可以遍历删除,删除的过程类似于以下代码(遍历删除521):


int key=521;

list::iterator it=l.begin();//l为std::list

    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 DingDongList;

DingDongList dd_list;///全部员工打开记录

DingDongList punish_list;//惩罚员工名单

long normal_time = 90000;//正常上班时间

int normal_avg = 45;//惩罚的年龄上限,包含45岁



 

员工的打卡记录类似于:

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第2张图片

 


场景描述完毕,我们开始处理

 

我们是个大厂子,有个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()方法的参数只有如下几种,并没有对反向迭代器的删除操作:


反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第3张图片

那怎么办呢?难道就无法删除反向迭代器的元素了?

通过分析STL的iterator和reverse_iterator源码实现,发现了一些东西:

 


 


反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第4张图片

首先是,反向迭代器是继承迭代器的

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第5张图片

反向迭代器的求值操作求的是 (*(--current)),即当前存储迭代器的前一个迭代器的值。

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第6张图片

->操作则是求*操作后,求地址。


这些事实说明,reverse_iterator的求值和->操作都返回的是--iterator位置的数据。或者说,当前反向迭代器里面的指针指向的是,所访问的元素的下一个元素。

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第7张图片

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第8张图片



怎么把reverse_iterator转换成iterator呢?毕竟erase()能够删除的是iterator。

经过查证,发现了

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第9张图片



 

那么,base()方法求到的是

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第10张图片

 

实际上我们要删除的是元素5,那么求出指向5的正向迭代器呢?

 

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第11张图片

分析代码我们知道,反向迭代器的++操作是指针往前挪动,即++rbegin()之后,reverse_iterator内部存储的指针就指向了元素5,然后求一下base()就是指向5的正向迭代器了(++rbegin()).base();

 



 

反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用)_第12张图片

那么,逆序删除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;

}

}


到此,利用反向迭代器删除某些元素的功能就完成了。迟到的年轻员工,等着扣工资吧

 

你可能感兴趣的:(反向迭代器reverse_iterator与正向迭代器iterator之间的转换(以及实例应用))