【C++】反向迭代器:反向迭代器的底层认识与模拟实现

🧑‍💻博主名称:鱼子星_

✅数据结构专栏:【数据结构】

✅算法竞赛专栏:【算法竞赛】

✅C++系列专栏:【C++从零开始系列】


前言

如果将当代处于叛逆期的青年和父母说成是"对着干"的关系,那么,我们就可以将C++中迭代器和反向迭代器看成是"父母"和"叛逆期的青年"。没错,C++中反向迭代器和普通迭代器的关系就是反着来,那是怎么个"反"呢?其实就是在遍历方向上是反着的,即反向迭代器的++的效果和普通迭代器--的效果一样,普通迭代器++是往容器后面走,而反向迭代器++则是往容器前面走。

本篇文章主要的核心是讲解反向迭代器的实现,如果对反向迭代器还有不理解的地方可以看我 【C++】string(上):string的基本使用 这篇文章,这篇文章更详细的讲解了反向迭代器"反"在哪里,以及其使用方式,在掌握了反向迭代器的和普通迭代器的区别和使用后,再来学习本文反向迭代的实现也不晚。

目录

    • 前言
    • [一. 反向迭代器的实现原理](#一. 反向迭代器的实现原理)
      • [1.1 适配器的设计模式](#1.1 适配器的设计模式)
      • [1.2 使用运算符重载](#1.2 使用运算符重载)
      • 🎯小结
    • [二. 反向迭代器的实现](#二. 反向迭代器的实现)
      • [2.1 反向迭代器的框架](#2.1 反向迭代器的框架)
      • [2.2 反向迭代器成员函数的实现](#2.2 反向迭代器成员函数的实现)
      • [2.3 反向迭代器在类中的实例化](#2.3 反向迭代器在类中的实例化)
    • [三. 总结](#三. 总结)

一. 反向迭代器的实现原理

1.1 适配器的设计模式

反向迭代器的实现和stack,queue的底层实现一样,使用了适配器的设计模式,即可以自行的选择它的底层是什么,但是反向迭代器使用的适配器就不是容器了,而是迭代器。这里要注意将迭代器和反向迭代器进行区分,迭代器就是平常使用的iterator,而反向迭代器是reverse_iterator。

前面既然说了反向迭代器和迭代器之间是对着干的关系,那为什么反向迭代器的实现还是可以使用迭代器呢?原因在于,虽然说反向迭代器和迭代器的遍历方向和遍历结果是相反的,但是它们本质上都还是在遍历容器,所以底层的实现是可以直接复用的,且除了没有迭代器的容器,其它所有的容器都实现了迭代器,直接复用这些实现好的迭代器也可以减少代码量,提升程序的可读性。

下面我们来看 sgi3.0 版本的STL源码中对于反向迭代器实现的部分:

cpp 复制代码
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class RandomAccessIterator, class T, class Reference = T&,
          class Distance = ptrdiff_t> 
#else
template <class RandomAccessIterator, class T, class Reference,
          class Distance> 
#endif
class reverse_iterator 
{
  typedef reverse_iterator<RandomAccessIterator, T, Reference, Distance> self;
protected:
  RandomAccessIterator current;
public:
	reverse_iterator() {}
	explicit reverse_iterator(RandomAccessIterator x) : current(x) {}
};

这段代码是随机迭代器类型的反向迭代器,可以看到其模板参数中的RandomAccessIterator就是适配器的接口,可以使用任意的随机迭代器作为参数传递。而从类中唯一的成员变量current不难推断出,反向迭代器的底层实现依靠的也就是作为模板参数传递的迭代器。

反向迭代器的不仅仅只有这一个版本,还有一个只有一个模板参数的版本(见图 1-1),它只传递了一个模板参数是因为用到了迭代器萃取的方法,而再另外一个版本就是传递四个模板参数的双向迭代器的版本(见图 1-2)。


图 1-1 一个模板参数实现的反向迭代器


图 1-2 四个模板参数实现的双向反向迭代器

1.2 使用运算符重载

不过,如果仅仅只是将迭代器作为反向迭代器的底层还不够,因为反向迭代器的遍历方向要和迭代器相反。举个例子,假设此时有数据1,2,3,4,5,此时各有一个迭代器和反向迭代器指向3,这两个迭代器都执行++操作,反向迭代器会指向2,迭代器会指向4。

所以,反向迭代器实现的另外一个关键点就是运算符重载,通过运算符重载使得反向迭代器和迭代器的遍历方向相反,才是真正的形成了反向迭代器。

🎯小结

反向迭代器的实现也使用了适配器的设计模式,它的底层是通过复用迭代器来实现,通过运算符重载实现与迭代器遍历的方向相反的反向迭代器,简单来说,反向迭代器的实现就是将迭代器类型进行封装,再重载出和迭代器名字相同,效果相反的函数,就是为反向迭代器。

二. 反向迭代器的实现

2.1 反向迭代器的框架

这里模拟实现的反向迭代器为四个模板参数的版本的,不过这里不对随机迭代器和双向迭代器进行区分,因为如果是双向迭代器的反向迭代器要使用+,-等操作时,它自身的迭代器就会报错。这又印证了用迭代器做反向迭代器的底层的好处。

cpp 复制代码
namespace yzx
{
    template<class Iterator, class T, class Ref, class Ptr>
    class reverse_iterator
    {
        typedef reverse_iterator<Iterator, T, Ref, Ptr> Self;
    public:
        reverse_iterator(Iterator it)   // 构造函数 只能使用迭代器类型进行构造
            :_it(it)
        {}
    private:
        Iterator _it;   //成员遍历 以迭代器类型为底层实现反向迭代器
    };
}

2.2 反向迭代器成员函数的实现

cpp 复制代码
Self& operator++()
{
    --_it;
    return *this;
}

Self operator++(int)
{
    Self tmp(*this);
    --_it;
    return tmp;
}
cpp 复制代码
Self& operator--()
{
    return ++_it;
}

Self operator--(int)
{
    Self tmp(*this);
    ++_it;
    return tmp;
}
cpp 复制代码
bool operator==(const Self& rit)
{
    return _it == rit._it;
}

bool operator!=(const Self& rit)
{
    return _it != rit._it;
}
cpp 复制代码
Ref operator*()
{
    Iterator tmp(_it);
    return *(--tmp);
}

Ptr operator->()
{
    return &(operator*());
}

这里讲解一下opreator*为什么不是直接解引用_it而是解引用 _it的前一个位置,这是为了让反向迭代器和迭代器完成保持对称的关系所设定的。

这里对称的意思是让rend和begin指向同一个位置,rbegin和end指向同一个位置(如图 2-1),而因为普通迭代器end指向的位置没有有效元素,只有在它的前一个位置才开始有有效元素,且rbegin返回的迭代器解引用得到的是最后一个元素的值,所以此时operator*需要先--tmp再去解引用才能拿到正确的值。


图 2-1 反向迭代器和迭代器保持的对称性

2.3 反向迭代器在类中的实例化

cpp 复制代码
typedef reverse_iterator<iterator, T, T&, T*> reverse_iterator;
typedef reverse_iterator<const_iterator, T, const T&, const T*> 
const_reverse_iterator;
// 反向迭代器
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
// const反向迭代器
const_reverse_iterator rbegin() { return const_reverse_iterator(end()); }
const_reverse_iterator rend() { return const_reverse_iterator(begin()); }

三. 总结

反向迭代器的本质就是复用各个容器的普通迭代器,或者说将普通迭代器进行封装。当使用反向迭代器++时,就在内部调用普通迭代器的--以此来达到反向遍历的效果。

同时,为了确保反向迭代器与迭代器完全"相反",我们让rbegin == end,rend == begin,而要维护这个效果,就得改变解引用的逻辑,提前一个位置解引用刚好使得各个位置的值可以正常拿到。

相关推荐
geovindu几秒前
rust:N-Barrier Pattern
开发语言·后端·设计模式·rust·屏障模式·并发型模式
AI宇宙狂想家5 分钟前
开源 AI 视频工具进化史:从难用到好用,OpenMontage 改了什么
开发语言·前端·人工智能·开源·ai创业·openmontage
无心水15 分钟前
20、大数据架构设计理论与实践 核心考点与学习笔记
大数据·笔记·学习·架构师·系统架构设计师·软考系统架构设计师·架构设计师
大鹏说大话19 分钟前
和小鹅通相近的知识付费小程序平台有哪些
开发语言
Dovis(誓平步青云)23 分钟前
启元 T1 Pro 视觉伺服实践:基于 HSV 阈值与针孔测距的启元机器人寻物实现
开发语言·人工智能·机器学习·二次开发
weixin1997010801628 分钟前
官方授权通道和灰色爬虫,做数据服务该选哪条路(附 python 源码)
c++·爬虫·python
wuminyu33 分钟前
JVM执行引擎学习总结:热点字节码编译成本地机器码
java·linux·c语言·jvm·c++
snysy34 分钟前
WordPress伪静态404故障排查笔记
android·笔记·wordpress
Ryan-Zhong35 分钟前
数字人口播视频 API 实战:Python 跑通模板合成、异步轮询与批量出片
开发语言·python·音视频