【C++之容器适配器】反向迭代器的实现

news2024/9/28 7:22:21

目录

    • 前言
    • 一、反向迭代器的实现
      • 1. 底层
      • 2. 成员函数
        • 1. 构造函数
      • 2. operator*()
        • 3. operator->()
        • 4. 前置++
        • 5. 后置++
        • 6. 前置--
        • 7. 后置--
        • 8. operator!=()
        • 9. operator==()
    • 二、vector反向迭代器的实现
      • 1. vector的正向迭代器
      • 2. vector反向迭代器的实现
      • 3. 测试vector的反向迭代器
    • 三、list反向迭代器的实现
      • 1. list的正向迭代器
      • 2. list反向迭代器的实现
      • 3. 测试list的反向迭代器

前言

前面我们学习的C++中适配器模式的一个例子:栈和队列的模拟实现,C++中的适配器模式本质上是一种代码复用的手段,其可以实现相同功能代码的复用,而不会写出一些重复的代码,从而造成代码的冗余。C++中另一个适配器模式的经典设计就是反向迭代器的封装,其通过适配正向迭代器的功能,从而实现了反向迭代器的功能,本篇文章将重点讲解C++中反向迭代器的适配模式,并以list和vector的反向迭代器为例。

一、反向迭代器的实现

1. 底层

反向迭代器的底层是适配了正向迭代器,所以反向迭代器的成员变量是正向迭代器。其形式如下:

template<class Iterator,class Ref,class Ptr>
	struct Reverse_iterator
	{
		Iterator _it;
	}

2. 成员函数

1. 构造函数

反向迭代器中只有一个成员变量:正向迭代器,所以我们需要实现一个带参的构造函数将正向迭代器作为参数来对反向迭代器的成员进行初始化。

// 构造函数
		Reverse_iterator(const Iterator& it)
			:_it(it)
		{}

2. operator*()

在正向迭代器中,对正向迭代器进行解引用访问的是这个迭代器指向的结点中的数据,但是反向迭代器和正向迭代器有所不同,反向迭代器调用operator*()之后访问的是当前反向迭代器指向的结点的前一个结点的数据。

// * 
		Ref operator*()
		{
			Iterator tmp(_it);
			return *(--tmp);
		}

3. operator->()

operator->()的作用是求当前迭代器指向的结点中的数据的地址,所以可以通过调用operator*()之后再取地址,也可以直接返回上一个结点的数据的地址。

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

4. 前置++

正向迭代器中的++是朝正方向走,单向迭代器是朝着反方向走,所以可以通过正向迭代器中的–进行实现

// ++
		Self& operator++()
		{
			--_it;
			return *this;
		}

5. 后置++

当反向迭代器的前置++实现了之后,后置++可以通过调用前置++的功能完成

// ++
		Self& operator++()
		{
			--_it;
			return *this;
		}

6. 前置–

正向迭代器中的–是朝反方向走,单向迭代器是朝着正方向走,所以可以通过正向迭代器中的++进行实现

	// --
		Self& operator--()
		{
			++_it;
			return *this;
		}

7. 后置–

当反向迭代器的前置–实现了之后,后置–可以通过调用前置–的功能完成

Self operator--(int)
		{
			Self tmp(*this);
			--(*this);
			return tmp;
		}

8. operator!=()

反向迭代器的!=可以通过正向迭代器的!=进行判断

	// !=
		bool operator!=(const Self& it)
		{
			return _it != it._it;
		}

9. operator==()

反向迭代器的operator==() 可以通过正向迭代器的 operator==()进行判断

// ==
		bool operator==(const Self& it)
		{
			return _it == it._it;
		}

二、vector反向迭代器的实现

1. vector的正向迭代器

vector的底层是一块连续的数组,其迭代器本质上是原生指针

// 正向迭代器
		typedef T* iterator;
		typedef const T* const_iterator;

	// 正向迭代器
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

2. vector反向迭代器的实现

// 反向迭代器
		typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef Reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;

 // 反向迭代器
		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(end());
		}
		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(begin());
		}

		reverse_iterator rbegin() 
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

通过上面的代码我们不难看出,vector的反向迭代器的实现本质上就是将正向迭代器传给我们实现好的反向迭代器的第一个模板参数作为适配器,然后根据传的类型是普通类型还是const类型从而将反向迭代器分为普通反向迭代器和const版本的反向迭代器。

在实现反向迭代器中的rbegin()和rend()的时候需要注意:因为operator*()访问的是当前反向迭代器的上一个结点的数据,所以这里的rbegin()是正向迭代器的end()去适配得到的反向迭代器,当该反向迭代器进行解引用的时候,得到的是上一个结点的数据,也就是链表中的最后一个结点。rend()是正向迭代器中的begin()适配得到的反向迭代器,当该反向迭代器进行解引用的时候,得到的是上一个结点的数据,也就是头结点。

3. 测试vector的反向迭代器

  • 代码:

void test_vector_iterator()
{
	hjt::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);

	hjt::vector<int>::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << " ";
		vit++;
	}
	cout << endl;

}
  • 运行结果:
    在这里插入图片描述

三、list反向迭代器的实现

1. list的正向迭代器

list是一个带头双向循环链表,其底层是一个个的结点链接起来的,比赛顺序存储的容器,所以原生指针的行为无法完成迭代器对应的功能,所以list的正向迭代器我们需要自己封装一个自定义类型加上运算符重载实现对迭代器的*和->操作。具体实现我们再list中已经详细讲解,这里只附上对应代码:

	// list正向迭代器的封装
	template <class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T, Ref, Ptr> Self;
	public:
		// 成员函数
		// 构造函数
		__list_iterator(Node* node)
			:_node(node)
		{}

		// *
		Ref operator*()
		{
			return _node->_data;
		}
		// ->
		Ptr operator->()
		{
			return &_node->_data;
		}

		// ++
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		// --
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		// !=
		bool operator!=(const Self& it) const
		{
			return _node != it._node;
		}

		// ==
		bool operator==(const Self& it) const
		{
			return _node == it._node;
		}


		// 成员变量
		Node* _node;
	};
  • list中的代码
// 正向迭代器
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_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);
		}

2. list反向迭代器的实现

// 反向迭代器
		typedef Reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef Reverse_iterator<const_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());
		}

在实现反向迭代器中的rbegin()和rend()的时候需要注意:因为operator*()访问的是当前反向迭代器的上一个结点的数据,所以这里的rbegin()是正向迭代器的end()去适配得到的反向迭代器,当该反向迭代器进行解引用的时候,得到的是上一个结点的数据,也就是链表中的最后一个结点。rend()是正向迭代器中的begin()适配得到的反向迭代器,当该反向迭代器进行解引用的时候,得到的是上一个结点的数据,也就是头结点。

3. 测试list的反向迭代器

  • 代码:
void test_reverse_iterator()
{
	hjt::list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	lt.push_back(5);
	lt.push_back(6);

	// 使用反向迭代器进行遍历
	hjt::list<int>::reverse_iterator rlit = lt.rbegin();
	while (rlit != lt.rend())
	{
		cout << *rlit << " ";
		rlit++;
	}
	cout << endl;

}
  • 运行结果:
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349545.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git提交

文章目录关于数据库&#xff1a;桌面/vue-admin/vue_shop_api 的 git 输入 打开 phpStudy ->mySQL管理器 导入文件同时输入密码&#xff0c;和文件名 node app.js 错误区&#xff1a; $ git branch // git branch 查看分支 只有一个main分支不见master解决&#xff1a; gi…

PyQt5保姆级入门教程——从安装到使用

目录 Part1&#xff1a;安装PyQt5 Part 2&#xff1a;PyCharm配置PyQt5 Part 3&#xff1a;PyQt5设计界面介绍 Part 4&#xff1a;PyQt5设计UI 今天看了多个大佬的教程&#xff0c;总算是把PyQt5开发弄好了&#xff0c;每个部分都要看几个人的十分不方便&#xff0c;我十分…

YOLOv3简介

YOLOv3 预测部分 Darknet-53 YOLOv3的主干提取网络为Darknet-53&#xff0c;相比于YOLOv2时期的Darknet-19&#xff0c;其加深了网络层数且引入了Residual残差结构。其通过不断的1X1卷积和3X3卷积以及残差边的叠加&#xff0c;大幅度的加深了网络。残差网络的特点是容易优化&a…

【Unity VR开发】结合VRTK4.0:将浮点数从交互器传递到可交互对象

语录&#xff1a; 愿你熬得过万丈孤独&#xff0c;藏得下星辰大海。 前言&#xff1a; 默认情况下&#xff0c;交互器只能将单个布尔操作传递给可交互对象&#xff0c;后者控制可交互对象上的抓取操作。在其他时候&#xff0c;交互器中的其他操作可能希望传递给可交互对象&…

leaflet 设置marker,并可以任意拖动每一个marker(071)

第071个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中通过L.marker来添加marker,通过设置其属性,可以让marker在地图上任意的拖动。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共76行)相关API参…

实体店店铺管理软件怎么挑?看了排名就知道!

很多实体店店主在选择店铺管理软件时,不知道怎么选择,其实这个不难。一般根据市场上的排名也选择就ok了&#xff0c;因为一款被大家都认证过好用的软件&#xff0c;怎么都比盲选的或者名不见经传的软件好。选择一款适合的实体店店铺管理软件可以省很多事。而截止现在管理软件排…

linux将新加磁盘绑挂载到指定目录

查看当前挂载情况df -l此时可以看到sda和sdb两块磁盘已经被挂载&#xff0c;但实际上还有更多块磁盘未被挂载&#xff08;磁盘名称sda&#xff0c;结尾字母安顺递增&#xff09;查看一安装的所有磁盘fdisk -l此时我们可以看到还有很多未进行分区磁盘为磁盘添加分区fdisk /dev/s…

【另辟蹊径】Table 单元格内容过多之省略展示方案,设置Element table的 showOverflowTooltip 属性无效后的替代方案

一、问题背景 设置了element table的组件 <el-table-column>属性showOverflowTooltip无效&#xff0c;如图所示。 PS&#xff1a;注意不是不起作用&#xff0c;是有作用但是内容过多展示占据了整个界面&#xff0c;影响美观和用户体验。 有的博主解决方法是全局样式文件…

一眼万年的 Keychron 无线机械键盘

一眼万年的 Keychron 无线机械键盘 一款好的键盘对于程序员或者喜欢码字的人来说是非常重要的&#xff0c;而最近博主入手了自己的第一款机械键盘——Keychron 无线机械键盘。 机械键盘特点 有独立轴体&#xff0c;通过两个簧接触&#xff0c;来触发信号&#xff0c;价格相对贵…

大文件上传如何做断点续传?

一、是什么 不管怎样简单的需求&#xff0c;在量级达到一定层次时&#xff0c;都会变得异常复杂 文件上传简单&#xff0c;文件变大就复杂 上传大文件时&#xff0c;以下几个变量会影响我们的用户体验 服务器处理数据的能力请求超时 网络波动 上传时间会变长&#xff0c;高…

缓存穿透-总结

目录 缓存穿透-总结 出现场景&#xff1a; 解决方法&#xff1a; 方法1.缓存空对象&#xff1a; 方法2.加一个布隆过滤器&#xff1a; 总结&#xff1a; 缓存穿透-总结 出现场景&#xff1a; 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#xff0c;这样缓…

光量子领域新突破:有望打造芯片工厂!

将2D材料与氮化硅谐振器混合集成&#xff0c;使一系列单光子源与硅基光子按需精准结合。&#xff08;图片来源&#xff1a;网络&#xff09;量子光子学的著名专家、电气和计算机工程助理教授Galan Moody的实验室成功创造了一种在芯片上产生单光子的新方法。量子具有叠加态的特性…

飞桨特色产业级模型库,助力AI开发与落地更简单!

飞桨在长期的产业实践中发现&#xff0c;开发者使用开源模型项目落地普遍会遇到三大难题&#xff1a; 算法和模型繁多&#xff0c;做模型选择是个难题&#xff1b; 模型效果不错&#xff0c;但产业落地时容易遇到资源限制和部署的问题&#xff1b; 面对新场景无从下手&#x…

minio public桶禁止在直接访问桶位置时列出所有文件url

minio的public桶因为没有限制&#xff0c;所以在直接访问到桶地址的时候会列出桶内所有文件的url&#xff0c;这样很不安全&#xff0c;如何禁止这个功能&#xff0c;可以使用三种方法 1、如果是新版的可以直接设置桶的Access Policy为自定义就好 编辑custom的Policy&#xff…

五种情况下企业需要引进低代码开发平台

随着低代码开发平台的热度在上升&#xff0c;企业中也开始流行一种新的应用交付方式&#xff1a;业务部门基于低代码开发平台将所需要的功能&#xff08;或简单的可用版本&#xff09;自行搭建出来&#xff0c;当遇到较为复杂的需求时&#xff0c;则向IT部门请求支援。业务与IT…

【MFC】模拟采集系统——数据绘制(19)

完成界面设计后&#xff0c;数据绘制也可以按照对MFC类派生来完成&#xff0c;值得注意的是这里的数据绘制仅仅是通过随机产生的数据来显示&#xff0c;并且显示的方法也有很多。 数据绘制 在主对话框中添加两个 Picture Control 位置大小任意&#xff0c;可以设置一下外观&a…

Python3 数据结构实例及演示

本章节主要结合前面所学的知识点来介绍Python数据结构。 列表 Python中列表是可变的&#xff0c;这是它区别于字符串和元组的最重要的特点&#xff0c;1句话概括即&#xff1a;列表可以修改&#xff0c;而字符串和元组不能。 以下是 Python 中列表的方法&#xff1a; 下面示…

阿里云服务器部署前后端分离项目

阿里云服务器部署 【若依】 前后端分离项目 文章目录一、域名解析二、服务器操作系统置空三、部署方式四、需安装环境配置五、Linux服务器安装相应内容&#xff08;具体安装步骤&#xff09;&#xff08;一&#xff09;安装JDK&#xff08;3种方式&#xff09;使用Yum安装&…

Assignment写作各个部分怎么衔接完美?

Assignment格式很简单&#xff0c;就只有四个部分&#xff0c;按着通用的套路来&#xff0c;发现也没什么难度。不过这4个部分自己需要衔接完美&#xff0c;下面就给大家分享一下写Assignment最简单的方法。 如果没有目录可以放在第一页的开头&#xff0c;用“标题字体”加重显…

互联网大厂Java岗最全八股文面试1100道真题汇总,堪称2023年面试天花板

2023 年的互联网行业竞争越来越严峻&#xff0c;面试也是越来越难&#xff0c;一直以来我都想整理一套完美的面试宝典&#xff0c;奈何难抽出时间&#xff0c;这套 1100道的 Java 面试手册是行业内各大神联合总结出来的&#xff0c;上传到 Git 上目前 star 数达到了 30K 这套互…