【c++】:反向迭代器适配器:每天学一点点优秀的思想

简介: 【c++】:反向迭代器适配器:每天学一点点优秀的思想

前言



反向迭代器的适配只用于双向迭代器,对于单链表实现的单向迭代器是不能通过适配构造一个反向迭代器的,为什么要说反向迭代器适配器呢?因为我们只需要实现一个反向迭代器模板就可以用所有的双向迭代器的正向实现其反向迭代器。


提示:以下是本篇文章正文内容,下面案例可供参考


一、list的反向迭代器



对于list而言,我们想要倒着遍历该如何实现呢?其实实现过正向迭代器的朋友们都知道,list的迭代器的++是指向链表的下一个节点,也就是node = node->next,那么要实现反向我们只需要将++实现为将node = node->prev即可,如下图所示:

66cf8c8f3d59410896177cf4a949f75f.png


    //反向迭代器
  template<class T, class Ref, class Ptr>
  struct list_reverse_iterator
  {
    typedef list_node<T> node;
    typedef list_reverse_iterator<T, Ref, Ptr> self;
    node* _node;
    list_reverse_iterator(node* n)
      :_node(n)
    {
    }
    Ref operator*()
    {
      return _node->_data;
    }
    self& operator++()
    {
      _node = _node->_prev;
      return *this;
    }
    self operator++(int)
    {
      self tmp(*this);
      _node = _node->_prev;
      return tmp;
    }
    self& operator--()
    {
      _node = _node->_next;
      return *this;
    }
    self operator--(int)
    {
      self tmp(*this);
      _node = _node->_next;
      return tmp;
    }
    Ptr operator->()
    {
      return &_node->_data;
    }
    bool operator!=(const self& it)
    {
      return _node != it._node;
    }
    bool operator==(const self& it)
    {
      return _node == it._node;
    }
  };


可以看到我们上述代码对于反向迭代器确实只是将++变成了原先的--,--变成了原先的++。现在我们再写一下rbegin(),rbegin().

class list
  {
  public:
    typedef list_node<T> node;
    typedef list_iterator<T, T&, T*> iterator;
    typedef list_iterator<T, const T&, const T*> const_iterator;
    typedef list_reverse_iterator<T, T&, T*> reverse_iterator;
    iterator begin()
    {
      return iterator(_head->_next);
    }
    iterator end()
    {
      return iterator(_head);
    }
    const_iterator begin() const
    {
      return const_iterator(_head->_next);
    }
    const_iterator end() const
    {
      return const_iterator(_head);
    }
    reverse_iterator rbegin() 
    {
      return reverse_iterator(_head->_prev);
    }
    reverse_iterator rend() 
    {
      return reverse_iterator(_head);
    }



我们先将反向迭代器重命名为reverse_list,然后让rbegin()变成链表的尾节点,rend()还是链表的哨兵位头结点不变,下面我们来验证一下看是否正确:

802e04367fc24aaead6aed185b7773e9.png

通过验证我们发现确实可以实现反向迭代,那么现在问题来了,我们刚开始说了反向迭代器是一个适配器,如果我们用这个链表的反向迭代器去实现vector的反向迭代器可以实现吗?答案肯定是不行,因为vector又不是节点怎么返回节点的前一个后一个呢?所以我们实现的这个迭代器只能list使用,下面我们看看大佬是如何实现反向迭代器的:

ba9e44366e2d42e4b116da10076cdc9f.png


我们圈出来的current是什么呢?current是一个正向迭代器

7ba8e8ed327c4a0692d5464c824593f6.png


那么为什么operator*变成了--迭代器然后解引用呢?再往下看:

b138c3385ebb423b8bd36d3ac503bfa0.png


反向迭代器的++是正向迭代器的--,反向迭代器的--是正向迭代器的++。

b09ac5ed92df49af9150ece018941a81.png

我们发现rbegin()是迭代器的end(),rend()是迭代器的begin(),下面我们画个图来验证一下:

b8df1d1105f04f3386c6f4ec297cbd36.png



这个时候我们终于理解了为什么operator*是--iterator的解引用,因为rbegin()是指向哨兵位的头结点的,这个时候是不能对头结点解引用的,所以我们应该对头结点的prev也就是最后一个节点解引用,这样一来就能依次从后往前访问到链表的每个元素,当rbegin()==rend()的时候将所有元素遍历完成。

下面我们就来实现一下:

namespace sxy
{
  template<class iterator,class Ref,class Ptr>
  struct ReverseIterator
  {
    typedef ReverseIterator<iterator, Ref, Ptr> self;
    iterator _cur;
    ReverseIterator(iterator it)
      :_cur(it)
    {
    }
  };
}



首先我们创建一个iterator.h文件,然后写一个模板这个模板第一个参数是任意类型的迭代器,第二个参数是迭代器中解引用的返回值Ref,第三个参数是在重载->符号的返回值Ptr。然后我们用正向迭代器创建一个变量_cur,构造函数初始化的时候直接用正向迭代器初始化_cur.

然后我们先实现++,--:

        self& operator++()
    {
      --_cur;
      return *this;
    }
    self operator++(int)
    {
      iterator tmp = _cur;
      --_cur;
      return tmp;
    }


对于前置++和后置++的区别在于后置++要返回--前的那个值,所以我们直接用拷贝构造一个tmp,这里我们是没有实现迭代器的拷贝构造的,那么使用默认的拷贝构造可以吗?答案是可以,因为我们要的就是浅拷贝,我们就是要tmp指向cur位置,如下图:

58e39d68f0b64d6888514444a8655832.png


我们再看一下深拷贝变成了什么:

d1d7f75c777a44bf9636dea271a47f86.png

通过上图我们应该理解了为什么我们直接使用默认的构造函数就能解决问题。

        self& operator--()
    {
      ++_cur;
      return *this;
    }
    self operator--(int)
    {
      iterator tmp = _cur;
      ++_cur;
      return tmp;
    }



对于--的实现是和++一样的,只不过--变成了正向迭代器的++。

下面我们再实现一下迭代器要使用的==和!=符号:

        bool operator!=(const self& s)
    {
      return _cur != s._cur;
    }
    bool operator==(const self& s)
    {
      return _cur == s._cur;
    }



对于反向迭代器的判断我们只需要判断两个反向迭代器的节点是否相等即可。

下面我们实现反向迭代器的解引用:

        Ref operator*()
    {
      iterator tmp = _cur;
      --tmp;
      return *tmp;
    }



对于反向迭代器的解引用我们说过,由于rbegin()是在end()的位置所以我们是不能直接解引用的,正确的操作是解引用此位置的前一个节点(为什么是前一个呢?因为是反向迭代器!)。所以我们用_cur拷贝构造一个tmp,然后--tmp就是前一个节点,然后再返回解引用的值即可。返回值的实现与正向迭代器一样具体可以去看我的list模拟实现那篇文章看看为什么要多一个模板参数Ref做返回值。


下面我们在list中定义一下反向迭代器:

a3c5649e3be34331a62ee5251e77d54b.png


首先包含一下头文件,然后typedef一下反向迭代器和反向迭代器的const版本:

typedef ReverseIterator<iterator,T&,T*> reverse_iterator;
typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;
        reverse_iterator rbegin() 
    {
      return reverse_iterator(end());
    }
    reverse_iterator rend() 
    {
      return reverse_iterator(begin());
    }
    const_reverse_iterator rbegin() const
    {
      return const_reverse_iterator(end());
    }
    const_reverse_iterator rend() const
    {
      return const_reverse_iterator(begin());
    }



接下来我们看看是否能成功反向输出呢?


709f83d6a15b47e0b636a2f2b4c20036.png


看来我们是成功了,那么回到我们一开始的问题,可以用这个迭代器去适配vector的反向迭代器吗?我们画个图看看:

2a23c193eb8c4277914acc8c17559a04.png



当反向迭代器++的时候是正向迭代器的--,然后从vector的尾部开始向头部移动。同时我们发现,operator*的实现也是满足的,因为rbegin()的位置是vector存放最后一个有效数据的位置的下一个,这个位置是没有数据的,只有让迭代器--一下在解引用才是正确的值,由于是每次位置的前一个所以当rbegin()==rend()的时候将反向遍历完所有数据,下面我们就演示一下vector的反向迭代器:


vector的反向迭代器



首先在vector.h的头文件中包含iterator.h的头文件:

3d935443c2094165b45a7bcc32f1ce5d.png


然后在vector中typedef一下反向迭代器:

typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;
        reverse_iterator rbegin()
    {
      return reverse_iterator(end());
    }
    reverse_iterator rend()
    {
      return reverse_iterator(begin());
    }
    const_reverse_iterator rbegin() const
    {
      return const_reverse_iterator(end());
    }
    const_reverse_iterator rend() const
    {
      return const_reverse_iterator(begin());
    }


然后我们运行一下程序看是否能成功:

f2de274672404c5faf7e5c9649c601ed.png


答案我们也看到了确实可以,这也就证明了我们一开始所说的大佬思想。当我们想实现list的反向迭代器的时候只实现出针对list的反向迭代器,而大佬却用一个反向迭代器适配其他的反向迭代器。


总结



对于迭代器的适配器最重要的是实现的思想,比如我们如何控制对迭代器进行解引用,++--等等,想要有如同大佬一般的思想我们必须多看优秀的代码,只有去看别人写的优秀的代码我们才能进步。

目录
相关文章
|
6月前
|
设计模式 存储 Android开发
c++的学习之路:18、容器适配器与反向迭代器
c++的学习之路:18、容器适配器与反向迭代器
47 0
|
6月前
|
存储 程序员 C++
在C++语言中容器的适配器
在C++语言中容器的适配器
46 0
|
5月前
|
设计模式 存储 C++
【C++/STL】:stack/queue的使用及底层剖析&&双端队列&&容器适配器
【C++/STL】:stack/queue的使用及底层剖析&&双端队列&&容器适配器
68 2
|
6月前
|
存储 算法 C语言
从C语言到C++_38(C++的IO流+空间适配器)STL六大组件联系(下)
从C语言到C++_38(C++的IO流+空间适配器)STL六大组件联系
59 5
|
4月前
|
存储 算法 C语言
【C++】详解STL的适配器容器之一:优先级队列 priority_queue
【C++】详解STL的适配器容器之一:优先级队列 priority_queue
|
4月前
|
设计模式 存储 缓存
【C++】详解STL容器之一的deque和适配器stack,queue
【C++】详解STL容器之一的deque和适配器stack,queue
|
6月前
|
存储 算法 C语言
从C语言到C++_19(容器适配器+stack和queue模拟实现+优先级队列priority_queue)(下)
从C语言到C++_19(容器适配器+stack和queue模拟实现+优先级队列priority_queue)
53 2
|
5月前
|
存储 设计模式 算法
【C++航海王:追寻罗杰的编程之路】priority_queue(优先队列) | 容器适配器你知道哪些?
【C++航海王:追寻罗杰的编程之路】priority_queue(优先队列) | 容器适配器你知道哪些?
51 0
|
6月前
|
存储 设计模式 算法
【C++/STL】stack和queue(容器适配器、优先队列、双端队列)
【C++/STL】stack和queue(容器适配器、优先队列、双端队列)
45 1
|
6月前
|
算法 C语言 C++
从C语言到C++_38(C++的IO流+空间适配器)STL六大组件联系(上)
从C语言到C++_38(C++的IO流+空间适配器)STL六大组件联系
51 0