C++ [STL容器反向迭代器]

news2024/11/24 22:47:23

STL容器反向迭代器

本文已收录至《C++语言和高级数据结构》专栏!
作者:ARMCSKGT

CSDN


STL容器反向迭代器

  • 前言
  • 正文
    • 适配器
    • 反向迭代器
      • 反向迭代器框架
      • 默认成员函数
      • 反向迭代器的遍历
      • 反向迭代器的比较
      • 反向迭代器数据访问
      • 反向迭代器代码
      • 测试反向迭代器
  • 最后


前言

我们知道STL大部分容器都有迭代器,迭代器又分为正向迭代器和反向迭代器,对于正向迭代器以及实现前面我们已经了解了不少,而反向迭代器的设计思想是适配器模式,本节我们介绍反向迭代器的实现!
反向迭代器


正文

适配器


适配器是把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作!

那么到底什么是适配器?
电源适配器
我们常用的充电器就是一个例子,充电器也叫电源适配器,一个充电器可以充相同接口不同手机,这就是适配器!

STL中有三类迭代器

  • container adapters:容器适配器
    – 容器适配器底层套用其他容器作为底层容器,封装容器,改变容器的行为作为本容器适配器;容器适配器底层的容器可以修改,只要实现了容器适配器底层所需函数都可以作为容器适配器的底层容器;例如stack容器的底层是使用deque(双端队列),也可以修改为vector,在后面我们会着重介绍容器适配器

  • functor adapters:仿函数适配器
    – 如果说仿函数的设计是为了让必须有两个参数的普通函数能够被只能有传入一个参数的算法所使用的话,仿函数适配器则是为了让必须有两个参数的仿函数被算法调用;仿函数适配器非常厉害,几乎可以无限制的创造出各种可能的表达式

  • iterator adapters:迭代器适配器
    – 迭代器适配器主要是可以实现反向迭代器,反向迭代器独立实现与一个头文件,只要容器支持双向迭代器,都可以支持反向迭代器,反向迭代器是对正向迭代器的封装,改变其行为,满足条件的容器只需要声明反向迭代器的头文件便可以使用反向迭代器

适配器模式

图片出自《STL源码剖析》

反向迭代器


反向迭代器适用于所有的容器,因此它是作为一个单独的 .h 文件出现的,别的容器如果想使用,直接包含就行了!

反向迭代器 reverse_iterator 可以用来反向遍历容器,在某些场景下很实用!
reverse_iterator

图片出自《STL源码剖析》

反向迭代器框架

反向迭代器是对正向迭代器的封装,所以使用多模板参数!

模板参数:

  • Iterator:正向(普通)迭代器类型
  • Ref:迭代器下元素数据类型的引用类型
  • Ptr:迭代器下元素数据类型的指针类型


    之所以这样设计,也是为了简化代码,增强代码的复用性;对于 stringvectorlist,可以共用这一套代码就能同时实现反向迭代器。(对于其他容器,要么不支持迭代器或不支持双向迭代器,无法满足反向迭代器需求;又或之其容器迭代器底层实现复杂,需要单独实现,例如map和set)
    共用一套反向迭代器代码

在迭代器对象中,我们需要一个成员变量保存当前的正向迭代器,所以定义一个普通迭代器变量 current 保存正向迭代器,方便操作!

反向迭代器在 ++ 和 - - 后需要返回一个反向迭代器,为了书写简单,我们将反向迭代器类型在类中typedef,定义为 self

template<class Iterator,class Ref,class Ptr>
struct Reverse_iterator
{
	Iterator current; //迭代器对象

	typedef Reverse_iterator<Iterator, Ref, Ptr> self; //反向迭代器类型
};

注意:反向迭代器思想与正向迭代器一样,只是用于封装,改变被封装对象的行为;所以在反向迭代器中的大部分操作都会调用正向迭代器中的函数进行操作!


默认成员函数

对于默认成员函数,我们只需要实现构造和拷贝构造即可!

	//Reverse_iterator() {} //默认构造
	explicit Reverse_iterator(Iterator x) //构造封装迭代器 - 禁止类型转换
		:current(x)
	{}

	Reverse_iterator(const self& x) //自对象(reserve_iterator)构造-拷贝构造
		:current(x.current)
	{}

因为是反向迭代器是对正向迭代器的封装,我们严格要求传入的参数必须是正向迭代器,禁止在构造时类型转换,防止出现以外!


反向迭代器的遍历

反向迭代器分别为 rbegin 和 rend ,rbegin 是对 end 的封装,rend 是对 begin 的封装!

//反向迭代器
//普通反向迭代器
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
//const普通反向迭代器-应对特殊传参场景
const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
//const反向迭代器
const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator crend() const { return const_reverse_iterator(begin()); }

对于 vector<int> v = {1,2,3,4,5}; 遍历

  • 正向迭代器通过++遍历:1 2 3 4 5
  • 反向迭代器通过++遍历:5 4 3 2 1
    迭代器遍历

对于反向迭代器的遍历,从最后一个元素的下一个位置开始,反向迭代器的 ++ 就是对正向迭代器的 - -
因为我们封装的current就是正向迭代器,所以直接对current进行 ++ 或 - - 即可!

self& operator++()//前置++
{
	--current; //对于 ++ 先 -- 再返回迭代器
	return *this;
}

self operator++(int)//后置++
{
	self tmp(*this);
	--current;
	return tmp;
}

self& operator--() //前置--
{
	++current; //对于 -- 先 ++ 再返回对象
	return *this;
}

self operator--(int)//后置--
{
	self tmp(*this);
	++current;
	return tmp;
}

反向迭代器的比较

迭代器的比较,常用于遍历时的终止条件,即 == 和 != 比较!
对于这两种比较,直接调用正向迭代器的比较运算符即可!

// != 比较
bool operator!=(const self& n) const
{
	return current != n.current;
}
// == 比较
bool operator==(const self& n) const
{
	return current == n.current;
}
//直接调用正向迭代器的比较即可

反向迭代器数据访问

对于反向迭代器,我们也会使用 *-> 来访问数据,对于反向迭代器,访问数据就有所不同了!

前面我们提到,反向迭代器rbegin是封装end实现的,那么如果我们要访问反向遍历中的第一个元素,不能直接对反向迭代器中的正向迭代器调用 operator*()operator->() 否则就属于野指针访问了!

所以对于反向迭代器元素的访问,我们整体向前移动一位,即迭代器在end位置时,访问end-1位置的元素,这样既可以解决元素访问的问题,也可以解决了遍历时漏掉元素的问题!
迭代器遍历
如果不进行这样的设计,还会引发另一个问题:
在这里插入图片描述
如果按照我们想象的方式设计,遍历时第一个元素无法遍历到,因为当rbegin==rend时就退出了,于是设计者在设计时规定,迭代器访问元素时,默认访问迭代器位置的上一个位置元素,这样当迭代器处于2时,访问的就是1,这样就避免了这个问题且也不会出现rbegin越界访问问题!

// T&
Ref operator*() //解引用访问数据
{ //因为是反向迭代器,如果是begin访问则是end位置,此时需要先--再解引用
	Iterator tmp(current);
	--tmp;
	return *tmp;
}

// T*
Ptr operator->()
{
	Iterator tmp(current);
	--tmp;
	return &(operator*());
}

对于 operator->() 只需要复用 operator*() 对其返回值取地址即可!


反向迭代器代码

#pragma once
#include<iostream>
#include<assert.h>

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

	typedef Reverse_iterator<Iterator, Ref, Ptr> self;

	//Reverse_iterator() {} //默认构造
	explicit Reverse_iterator(Iterator x) //构造封装迭代器 - 禁止类型转换
		:current(x)
	{}

	Reverse_iterator(const self& x) //自对象(reserve_iterator)构造-拷贝构造
		:current(x.current)
	{}

	//反向迭代器 ++ 相当于 --
	self& operator++()//前置++
	{
		--current;
		return *this;
	}
	//返回本对象
	self operator++(int)//后置++
	{
		self tmp(*this);
		--current;
		return tmp;
	}

	self& operator--() //前置--
	{
		++current;
		return *this;
	}

	self operator--(int)//后置--
	{
		self tmp(*this);
		++current;
		return tmp;
	}

	bool operator!=(const self& n) const
	{
		return current != n.current;
	}

	bool operator==(const self& n) const
	{
		return current == n.current;
	}

	//T&
	Ref operator*() //解引用访问数据
	{ //因为是反向迭代器,如果是begin访问则是end位置,此时需要先--再解引用
		Iterator tmp(current);
		--tmp;
		return *tmp;
	}

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

本节简单的介绍反向迭代器的思想,反向迭代器中还有一些功能,感兴趣的小伙伴可以研究研究!


测试反向迭代器

我们将反向迭代器植入我们自己实现的string,vector和list中进行实验!

list

void test()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	list<int> x(arr,arr+10);
	auto it = x.rbegin();
	while (it != x.rend())
	{
		cout << *(it) << " ";
		++it;
	}
}

list反向迭代器测试

vector

void test()
{
	int arr[] = { 448,558,668,778,888 };
	vector<int> x(arr, arr + 5);
	auto it = x.rbegin();
	while (it != x.rend())
	{
		cout << *(it) << " ";
		++it;
	}
	cout << endl << endl;
}

vector反向迭代器测试

string

void test()
{
	string x("droW elloH");
	auto it = x.rbegin();
	while (it != x.rend())
	{
		cout << *(it) << " ";
		++it;
	}
	cout << endl << endl;
}

string反向迭代器测试

关于上面三个容器的测试代码如下:

  • string模拟实现
  • vector模拟实现
  • list模拟实现

代码仅简单实现,存在部分问题,敬请谅解!


最后

本节简单介绍了反向迭代器思想,将类和对象的封装意义体现的淋漓尽致,关于迭代器的介绍就告一段落,在后期,对于复杂容器,其迭代器的设计将会更加复杂,我们后面继续学习!

本次 <C++ STL容器反向迭代器> 就先介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
C-PLUS-PLUS

🌟其他文章阅读推荐🌟
C++ <STL之list模拟实现> -CSDN博客
C++ <STL之list使用> -CSDN博客
C++ <STL之vector模拟实现> -CSDN博客
C++ <STL之vector的使用> -CSDN博客
🌹欢迎读者多多浏览多多支持!🌹


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

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

相关文章

(2023最新版)互联网大厂1120道Java面试真题附答案详解

很多 Java 工程师的技术不错&#xff0c;但是一面试就头疼&#xff0c;10 次面试 9 次都是被刷&#xff0c;过的那次还是去了家不知名的小公司。 问题就在于&#xff1a;面试有技巧&#xff0c;而你不会把自己的能力表达给面试官。 应届生&#xff1a;你该如何准备简历&#…

5.3.4 因特网的路由协议(四)BGP协议

5.3.4 因特网的路由协议&#xff08;四&#xff09;BGP协议 我们学习的RIP协议&#xff08;5.3.2 因特网的路由协议&#xff08;二&#xff09;基于距离向量算法的RIP协议&#xff09;和OSPF协议&#xff08;5.3.3 因特网的路由协议&#xff08;三&#xff09;OSPF协议&#x…

Python真的对初学者友好吗?其实可以从以下几点就能看出(收藏)

本文内容里我给大家分享的是一篇关于学习python有哪些必要条件&#xff0c;需要的朋友们可以学习下。 编程零基础&#xff0c;可以学习 Python 吗&#xff1f;这是很多初学者经常问我的一个问题。 当然&#xff0c;在计算机方面的基础越好&#xff0c;对学习任何一门新的编程…

强制使用本地GNSS作为时钟源带来的思考

1.背景知识 BMCA&#xff08;最佳时钟源选择算法&#xff09;&#xff1a;它是在PTP网络里面用来选择最佳时钟源的一种常见算法&#xff0c;它的执行过程包含一下四步&#xff1a; 时钟源发现&#xff1a;在网络中的PTP设备会交换时钟源信息。每个设备会公告自己的时钟源特性&…

联想拯救者电脑触摸板用不了了

文章目录 问题分析解决1. 解决方法一2. 解决方法二3. 解决方法三 问题 电脑触摸板用不了了&#xff0c;无论使用怎样的操作均未能完成对鼠标的操作 分析 这是因为被误触了“游戏模式”&#xff0c;就会出现“防误触”开关 解决 1. 解决方法一 &#xff08;开机输入密码前…

汽车EDI:如何与SAS建立 EDI 连接?

SAS Automotive Systems &#xff08;以下简称为&#xff1a;SAS&#xff09;是一家全球领先的汽车零部件制造商&#xff0c;总部位于德国。该公司专注于汽车电子技术和系统集成领域&#xff0c;为世界各大汽车制造商提供创新的解决方案。 EDI&#xff08;电子数据交换&#x…

MFC扩展库BCGControlBar Pro v33.5亮点 - Ribbon Bar等全新升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v33.5已正式发布了&#xff0c;此版本包含了Ribbon&#xff08;功能区&#xff09;自定义…

『 前端三剑客 』:CSS常用属性

文章目录 一 . CSS常用元素属性1.1 字体家族和 字体大小1.2 设置字体粗细 font-weight1.3 文字样式1.4 文字颜色1.5 文本对齐1.6 文本装饰1.7 文本缩进1.8 背景属性1.9 边框设置 二 . 元素的显示模式2.1 块级元素2.2 行内元素2.3 css 盒子模型 三 . 弹性布局3.1 开启弹性布局3.…

springboot+vue地方美食分享网站(java项目源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的地方美食分享网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风…

Python基础(9)——Python运算符

Python基础&#xff08;9&#xff09;——Python运算符 文章目录 Python基础&#xff08;9&#xff09;——Python运算符目标运算符的分类1. 算数运算符2. 赋值运算符3. 复合赋值运算符4. 比较运算符5. 逻辑运算符5.1 拓展 总结 目标 掌握常用运算符的作用 运算符的分类 算数…

weboffice获取外部剪切板内容解决方案

1、初始化引入sdk时&#xff0c;需要传入监听函数 -- getClipboardData 在移动端 APP 需要从系统剪切板获取数据时&#xff0c;可以使用该接口。 目前仅支持移动端表格以及文字&#xff0c;并且 JSSDK 版本为 v1.1.6 可以通过传入 获取系统剪切板数据函数 在文档粘贴的时候&…

「深度学习之优化算法」(四)遗传算法

1. 遗传算法简介 遗传算法(Genetic Algorithms,GA)是一种模拟自然中生物的遗传、进化以适应环境的智能算法。由于其算法流程简单,参数较少优化速度较快,效果较好,在图像处理、函数优化、信号处理、模式识别等领域有着广泛的应用。   在遗传算法(GA)中,每一个待求问题…

“鸡兔同笼”问题蕴含的数学思维、数学思想

郭靖 [摘 要]“鸡兔同笼”问题是我国古代数学里的经典问题&#xff0c;出自《孙子算经》&#xff0c;也是小学数学的拓展内容。“鸡兔同笼”问题是一类题的总述&#xff0c;其背后隐藏着不同的解题策略与思维。教师应剖析由“鸡兔同笼”问题延伸出来的解题思路与思考方式&…

社交电商以人为核心,流量营销矩阵该如何打造

​ 从流量运营的角度来看&#xff0c;社交电商和传统电商最大的区别在于&#xff0c;社交电商在一开始就不是以流量为核心&#xff0c;而是以人为核心。 具体来说&#xff0c;就是通过社交关系链把消费者串联起来&#xff0c;打造一个巨大的流量池。在这个流量池中&#xff0…

经典游戏|像素鸟

基于C#制作一个经典的像素风休闲娱乐小游戏|像素鸟。 一、项目搭建1.1、创建1.2、界面设计二、功能实现2.1、初始化游戏2.2、绘制管道2.3、地面移动2.4、控制小鸟下落的重复执行事件2.5、结语一、项目搭建 1.1、创建 打开Visual Studio,右侧选择创建新项目。 搜索框输入winf…

AI实景三维建模开放网页版,无需下载客户端,照片/视频转3D模型

近年来&#xff0c;随着实景中国建设进程的推进&#xff0c;从日常生活中道路、桥梁等基础设施的实景三维数字化&#xff0c;到虚拟现实、元宇宙数字展厅、虚拟数字人等互联网新基建参与方的不断增加&#xff0c;对现实物品进行1:1建模再映射到数字空间中的需求领域已经越来越广…

Docker部署(3)——Dockerfile文件参数

一、Dockerfile文件参数 Dockerfile 是用于构建 Docker 镜像的文件(之前的项目就是将jar包通过Dockerfile文件(D要记得大写&#xff01;&#xff01;)&#xff0c;打包成一个镜像&#xff0c;当然后面也有一键化部署&#xff0c;使用插件来完成&#xff0c;方式有很多&…

Benewake(北醒) TF-LC02 (TTL) 雷达不使用TTL转USB转接板在Arduino Uno上的运用

目录 前言Benewake(北醒) TF-LC02产品简要说明Arduino开发板介绍Benewake(北醒) TF-LC02 接口及通讯协议说明接口定义串口协议说明通讯协议说明功能码说明 接线示意图例程说明配置软硬串口定义获取TOF数据的结构获取雷达距离数据的协议解析通过主循环发送获取距离指令&#xff…

信号AWGN噪声添加及SNR计算(matlab完整代码)

SNR&#xff08;Signal-to-Noise Ratio&#xff0c;信噪比&#xff09;是与信号质量相关的重要指标。它衡量了信号与噪声之间的相对强度&#xff0c;在计算 SNR之前&#xff0c;通常需要进行校准和对齐&#xff0c;以确保接收到的信号与理想信号具有相同的参考点和相位&#xf…

第三方库介绍——zlib库

文章目录 zlib1. zlib库介绍2. zlib库的应用3. 下载地址4. 函数使用教程4.1 compress 与 uncompress4.3 使用过程解析4.2 infate、deflate、z_stream 5. 交叉编译zlib库 zlib 1. zlib库介绍 zlib是一套通用的解压缩开源库&#xff0c;提供了内存&#xff08;in-memory&#x…