【C++】STL——反向迭代器的模拟实现:迭代器适配器

news/2024/11/26 4:37:38/

文章目录

    • 前言
    • 1. list 的反向迭代器模拟实现
    • 2. 思考
    • 3. 库里面反向迭代器的实现——迭代器适配器
    • 4. 反向迭代器模拟实现的改进——适配器模式
    • 5. 适配器模式的实现——一劳永逸

前言

反向迭代器的使用相信大家都已经比较熟悉了,那我们这篇文章具体讲什么呢?
🆗,这篇文章我们重点来讲一下反向迭代器的模拟实现
那为什么我们之前不和正向迭代器放在一块讲呢?为什么要等到我们讲完了容器适配器再来讲反向迭代器的模拟实现呢?
那这个问题我相信学完这篇文章大家就明白了。

1. list 的反向迭代器模拟实现

首先我们来回看一下我们之前模拟实现list的代码:

在这里插入图片描述
这是我们之前写的list的正向迭代器。

那现在大家思考一个问题:单从使用的角度来看,反向迭代器和正向迭代器有什么区别?

其实区别好像也不是很大,就是正向迭代器的++是从前往后走,而反向迭代器的++是从后往前走,那对于list来说正向++是_node = _node->_next;,那反向就应该是_node = _node->_prev;,反过来嘛。
当然如果支持- -的话- -也是反过来嘛。
然后其它的解引用,判断是否相等啥的不就是一样的操作了嘛。

所以,我们是不是就可以这样来实现反向迭代器:

在这里插入图片描述
直接把正向迭代器的代码拷贝一份,然后名字改一下,++和- -的操作改一下就行了。
然后list里面:
在这里插入图片描述
把反向迭代器的类型放进去。
然后是不是还要提供rbegin和rend啊:
rbegin应该返回最后一个元素的迭代器
rend应该返回
在这里插入图片描述
第一个元素的前一个,那对于list来说就是头结点嘛
在这里插入图片描述
在这里插入图片描述
那对应的代码就是这样。

哦豁,那我们的反向迭代器不就写好了嘛!

试一下:
在这里插入图片描述
哎呀,是不是没问题啊。
那这样看来,要实现一个反向迭代器好像也不难啊。

2. 思考

但是呢:

此时,我们跟那些高手们,大佬们的差距就体现出来了,上面这种方法可能是我们大多数人最先想到的一种比较简单的方法。
虽然好像也可以;但是,有没有什么缺陷呢?
🆗,这样写的话代码是不是比较冗余啊:
在这里插入图片描述
除了圈出来的部分这两类是不是没什么差别啊。
那想要进步的话,看优秀的代码是一种很好的方法,那我们接下来就来看一下真正的大佬写的代码是怎么样的。

3. 库里面反向迭代器的实现——迭代器适配器

在这里插入图片描述

🆗,我们来看一下库里面list的迭代器是如何实现的

在这里插入图片描述
我们看到,这里的反向迭代器包括const版本的,它们都是对reverse_iterator这个类模板的一个typedef,但是它们的模板参数却是传的正向迭代器。
这是怎么回事啊?而且我们在当前stl_list.h这个头文件里并没有找到reverse_iterator这个类模板的定义!
那我们就需要继续去源码里面找,所以看源码其实并不是一件舒服的事情,或者说是比较痛苦的。
reverse_iterator这个类模板的实现其实是在另一个头文件stl_iterator.h里面:
在这里插入图片描述
reverse_iterator 这个类呢,其实是一个适配器,是一个迭代器适配器。
reverse_iterator 是对普通的正向迭代器进行了一个适配,进行了一个封装
但是库里面实现的是比较复杂的,涉及一个迭代器萃取的东西,这个我们可以不用管。
我们后面实现会简化一点。
那我们来大致看一下它是怎么实现的:
在这里插入图片描述
那它封装正向迭代器的话,反向的++就是正向的- -嘛,反向的- -就是正向的++。这些我们都好理解。

但是我们看这个:

在这里插入图片描述
欸!它的解引用怎么不是返回当前结点的值啊,为什么返回的是- 1之后再解引用的值?
那按照我们上面的分析和理解,除了++和- -这些操作之外,其它的解引用,判断相等不相等这些是不是可以不用动啊。
那它这里是怎么搞的呢?跟我们上面的实现有什么不同吗?
那这时候我们就要去看它里面的实现了,我们回到stl_list.h看到它的rbegin和rend是这样的:
在这里插入图片描述
rbegin 是end的位置, rend 是begin的位置,我们拿begin和end来对比一下:
在这里插入图片描述
它们对应的位置是这样的:
在这里插入图片描述
那这样我们就看出来了,库里面应该是想追求一个对称

但是如果这样实现的话:

反向迭代器在解引用的时候如果还是直接去它对应的那个位置是不是就出问题了,就拿rbeign来说,我们看:
在这里插入图片描述
如果直接取rbegin解引用的值,是不是就取到头结点的值了,但是正确的情况rbegin解引用是不是拿到的是最后一个元素的值。

那它这里怎么解决呢?

就是上面我们看到的:
在这里插入图片描述
它的解引用是不是返回的是-1之后的值啊,正向迭代器-1就是取prev嘛,头结点的prev是不是就正好是最后一个元素啊。
那现在大家应该就理解这里的解引用为什么这样写了。
那对应这个图的话:
在这里插入图片描述
走到5的话,解引用访问的是4,到4访问的是3,3对应2,2对应1,走到1等于rend了,就结束了。

那了解了库里面的实现机制之后,我们就来按照库里面的方式来实现一下。

4. 反向迭代器模拟实现的改进——适配器模式

接下来我们按照库里面的方式来实现一下:

namespace yin
{template <class Iterator>struct Reverse_Iterator{typedef Reverse_Iterator<Iterator> self;Iterator _cur;Reverse_Iterator(Iterator it):_cur(it){}self& operator++(){--_cur;return *this;}self operator++(int){self tmp(*this);--_cur;return tmp;}self& operator--(){++_cur;return *this;}self operator--(int){self tmp(*this);++_cur;return tmp;}bool operator!=(const self& s){return _cur != s._cur;}};
}

首先上面这段代码大家应该都能看得懂,我就不过多解释了,上面我们已经实现了构造、++、- -和!=。

然后我们实现一下解引用*

那我们这里按库里面走,经过上面我们的分析我们知道这里解引用返回的是- -之后的值。
在这里插入图片描述
这样做。
但是这里的返回值是不是有点难搞啊,普通对象解引用返回引用,const对象解引用返回const引用。
那这个问题我们之前是不是提出了比较好的解决方式啊,可以增加一个模板参数去控制。这里我们不按库里面的方法去走了,它里面实现的比较复杂,其实它用我们这个方法也能搞,但它考虑到其它的一些原因搞的比较复杂,我们不用管。
在这里插入图片描述
当然我们之前讲的由于 重载->的缘故是不是又增加一个模板参数Ptr。我们现在还没重载->,不过可以也加上。

那现在之前我们单独写的那个__list_reverse_iterator的那个类就不需要了,我们这样搞:

在这里插入图片描述
然后rbegin和rend也要改一下:
在这里插入图片描述
那写到这里其实就好了,来试一下:
在这里插入图片描述
🆗,已经可以了。

那实现好了,我们再来探讨一点东西。

5. 适配器模式的实现——一劳永逸

我们刚才按库里面的方式,即适配器的模式又把我们的反向迭代器实现了一下。

但是呢:

大家可能会提出这样的疑问:
我们后面的改进,按照适配器模式去重新实现,与之前我们自己的方法相比,好像也没有牛🐂到那里去啊?
不还是需要我们自己手动去写一个类嘛。

那为什么还要这样搞呢?

🆗,那接下来就给大家解释一下这样做真正的牛逼之处
大家想一下,对于我们的list来说,我们使用最开始我们自己的方法去实现反向迭代器(拷贝一份正向迭代器的代码,进行一些简单修改),确实也可以。
但是,如果是vector呢?
我们还可以用那种方法去搞吗?
是不是就不行了啊,因为我们vector的迭代器是怎么搞的?
是不是直接使用的原生指针啊(因为原生指针的行为 完全符合我们的需求),只不过进行了一下typedef
在这里插入图片描述
++和- -这些我们都不需要自己去重载,因为原生的行为就符合需求。
那这样的话我们就没法像上面那样去搞了。

但是对于适配器的实现方式:

你给我一个list的正向迭代器,我可以给你适配出list的反向迭代器,那如果给一个vector的正向迭代器,能否适配出vector的反向迭代器呢?
🆗,当然也是可以的。
回想我们之前学的容器适配器,它们对应的底层容器仅限一种吗?
不是的,是不是只要支持指定的那些操作就可以作为其底层的适配容器啊。

那我们这里的迭代器适配器Reverse_Iterator是不是只要对应容器的迭代器支持++和–操作就可以进行适配啊。(因为里面反向的++需要复用正向的- -,反向- -复用正向++)
所以,对于任何一个容器,只要迭代器至少是双向的,都可以用Reverse_Iterator这个类模板去适配出其反向的迭代器

那我们常见的这些容器什么vector,list,string的反向迭代器不就都可以搞定了嘛。

所以,现在大家体会到了吗?

当我们还停留在思考去如何实现list的迭代器的时候,人家考虑的已经是如何做到一劳永逸,搞定所有容器的反向迭代器
这就是我们和真正的大佬,高手之间的差距。

那我们来试一下吧,那vector的反向迭代器搞出来:

那有了Reverse_Iterator这个迭代器适配器的模板类,我们现在想要适配出vector的反向迭代器,怎么搞?
很简单:
在这里插入图片描述
然后就可以使用了:
在这里插入图片描述
是不是就行了。
Reverse_Iterator是一个类模板,你给我任何容器的正向迭代器,只要支持++和- -,我就给你适配出反向迭代器来。

🆗,这才是它真正的牛逼之处。

在这里插入图片描述


http://www.ppmy.cn/news/94609.html

相关文章

关于 OpenCV 图像处理工具包 imutils 简单认知

写在前面 博文内容涉及 基本的图像处理工具包 imutils 的简单介绍以及使用Demo理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全心全意&#xff0c;永不停息。所有其它的路都是不完整的&#xff0…

python操作xml,实现增删改查

xml文件内容如下&#xff1a; <configuration><property name"1"><name>beyond</name><value>yanyu</value></property><property name"2"><name>beyond1</name><value>yanyu1</va…

【JavaSE】Java基础语法(二十四):时间日期类

文章目录 1. Date类2. Date类常用方法3. SimpleDateFormat类&#xff08;应用&#xff09; 1. Date类 计算机中时间原点 1970年1月1日 00:00:00 时间换算单位 1秒 1000毫秒 Date类概述 Date 代表了一个特定的时间&#xff0c;精确到毫秒 Date类构造方法 示例代码 publi…

排序算法——冒泡排序详解及优化

冒泡排序 排序的稳定性冒泡排序优化后的冒泡排序冒泡排序的复杂度 排序的稳定性 对于一个排序算法&#xff0c;假设两个相同的元素Ai和Aj 在排序前这两个元素满足条件i<j&#xff0c;即Ai在Aj之前 在排序后Ai仍在Aj之前&#xff0c;则称为排序算法为稳定排序 否则称这个算法…

电话号码的字母组合--狗屎内容勿看

1题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出…

Python 下载的 11 种姿势,一种比一种高级

今天我们一起学习如何使用不同的Python模块从web下载文件。此外&#xff0c;你将下载常规文件、web页面、Amazon S3和其他资源。 通过本文的学习&#xff0c;你将学到如何克服可能遇到的各种挑战&#xff0c;例如下载重定向的文件、下载大型文件、完成一个多线程下载以及其他策…

jmeter做接口压力测试_jmeter接口性能测试

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是免…

C++插件管理类(上)

文章目录 一、插件管理类的好处二、疑问&#xff1a;动态库已经是运行时候加载了&#xff0c;为啥还需要插件类来管理库&#xff1f;三、如何实现一个插件管理类&#xff1f;四、插件类实例五、提供一个动态函数库六、使用GetProcAddress函数获取动态库中的函数地址为空的原因七…