掘根宝典之C++普通迭代器和反向迭代器详解

简介

迭代器是一种用于遍历容器元素的对象。它提供了一种统一的访问方式,使程序员可以对容器中的元素进行逐个访问和操作,而不需要了解容器的内部实现细节。

C++标准库里每个容器都定义了迭代器,这迭代器的名字就叫容器迭代器

迭代器的作用类似于指针,可以指向容器中的某个元素,并通过操作迭代器来访问和操作该元素。通过迭代器,我们可以实现对容器的遍历、查找、修改等操作,大大增强了程序的灵活性和通用性。

声明普通容器迭代器

有迭代器的容器类型使用iterator和const_iterator类型来表示迭代器的类型

(下面我们会讲到反向迭代器,它的类型是reverse_iterator或者const_reverse_iterator)

我们可以看个例子

vector<int>::iterator it1;
//it1能读取和修改vector<int>的元素string::iterator it2;
//it2能读取和修改string的元素vector<int>::const_iterator it3;
//it3能读取vector<int>的元素,不能修改string的元素string::const_iterator it4;
//it4能读取string的元素,不能修改string的元素

const_iterator的对象和常量指针差不多,能读取但是不能修改它所指元素的值。相反,iterator的对象可读可写。

如果vector和string对象是个常量,只能使用const_iterator;

如果vector和string对象不是常量,则既可以使用iterator也可以使用const_iterator

迭代器范围

迭代器范围的概念是标准库的基础。

一个迭代器范围(iterator range)由一对迭代器表示,两个迭代器分别指向同一个客器中的元素或者是尾元素之后的位置(one past the last element)。

这两个迭代器通常被称为begin和end,或者是first和last(可能有些误导),它们标记了容器中元素的个范围。

虽然第二个迭代器常常被称为last,但这种叫法有些误导,因为第二个迭代器从来都不会指向范围中的最后一个元素,而是指向尾元素之后的位置。

迭代器范围中的元素包含first所表示的元素以及从first开始直至last(但不包含last)之间的所有元素。

这种元素范围被称为左闭合区间(left-inclusive interval),其标准数学描述为

[begin, end)

表示范围自begin开始,于end之前结束。迭代器begin和end必须指向相同的容器。end可以与begin指向相同的位置,但不能指向begin之前的位置。

对构成范围的迭代器的要求

如果满足如下条件,两个迭代器begin和end构成一个迭代器范围:

  1. 它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置
  2. 我们可以通过反复递增begin来到达end。换句话说,end 不在begin之前。
  3. 编译器不会强制这些要求。确保程序符合这些约定是程序员的责任。

使用左闭合范围蕴含的编程假定

标准库使用左闭合范围是因为这种范围有三种方便的性质。

假定begin和end 构成一个合法的迭代器范围,则

  • 如果begin与end相等,则范围为空
  • 如果 begin 与end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
  • 我们可以对begin递增若干次,使得begin==end

这些性质意味着我们可以像下面的代码一样用一个循环来处理一个元素范围,而这是
安全的:

while (begin != end) 
*begin = val;// 正确:范围非空,因此begin指向一个元素
//移动迭代器,获取下一个元素
++begin;

给定构成一个合法范围的迭代器begin和end,若begin==end,则范围为空。在此情况下,我们应该退出循环。如果范围不为空,begin指向此非空范围的一个元素。因此,在while循环体中,可以安全地解引用begin,因为begin必然指向一个元素。最后,由于每次循环对beain递增一次,我们确定循环最终会结束。

普通迭代器(iterator和const_iterator)

begin()和end()

begin()返回指向第一个元素(或第一个字符)的迭代器。如有下述语句:

//由编译器决定b和e的类型,我们下面会讲
//b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(), e=v.end();//b 和e的类型相同


end成员则负资返回指向容器尾元素的下一位置的送代器,也就是说,该迭代器指示的是容器的一个本不存在的“尾后”元素。这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。

特殊情况下如果容器为空,则begin和end返回的是同一个选代器。

一般来说,我们不清楚(不在意)迭代器准确的类型到底是什么。在上面的例子中,使用 auto关键字定义变量b和e,这两个变量的类型也就是begin和end的返回值类型,我们后面将对相关内容做更详细的介绍。

cbegin()和cend()

cbegin()cend()是在C++中用于迭代器的函数。

cbegin()函数返回一个常量迭代器,它指向容器的第一个元素。常量迭代器意味着不能通过该迭代器来修改容器中的元素。

cend()函数返回一个常量迭代器,它指向容器的最后一个元素的下一个位置。由于它指向最后一个元素的下一个位置,因此不能通过该迭代器访问容器中的元素。

这两个函数主要用于在循环中遍历容器的元素。使用常量迭代器可以确保不会意外修改容器的内容,从而提高代码的安全性。

这两个和begin()和end()其实差不多,只是cbegin()和cend()的返回值一定是const_iterator类型,begin()和end()的不一定是const_iterator,还可以是iterator,这个我们下面会讲

反向迭代器(reverse_iterator和const_reverse_iterator)

上面我们讲的是普通迭代器,接下来我们要讲反向迭代器

首先,反向迭代器类型是reverse_iterator或者const_reverse_iterator

反向迭代器就是在容器中从尾元素向首元素反向移动的选代器。对于反向迭代器,递增(以及递减)操作的含义会颠倒过来。递增一个反向选代器(++it)会移动到前一个元素;递减一个迭代器(--it)会移动到下一个元素。

除了forward_list之外,其他容器都支持反向迭代器。

我们可以通过调用rbegin,rend、crbegin和crend成员函数来获得反向迭代器。这些成员函数返回指向容器尾无素和首元素之前一个位置的迭代器。与普通迭代器一样,反向选代器也有const版本和非const版本。

下面的循环是一个使用反向迭代器的例子,它按逆序打印vec中的元素:

vector<int> vec =(0,1,2,3,4,5,6,7,8,9);
//从尾元素到首元素的反向迭代器
for (auto r_iter = vec.crbegin();   //将r iter绑定到尾元素r_iter != vec.crend();   // crend指向首元素之前的位置++r_iter)                 // 实际是递减,移动到前一个元素cout << *r_iter << endl;          // 打印 9,8, 7,... 0

虽然颠倒递增和递减运算符的含义可能看起来令人混淆,但这样做使我们可以用算法透明地向前或向后处理容器。

例如,可以通过向sort传递一对反向迭代器来将vector整理为递减序:

sort(vec.begin(), vec.end());//按“正常序”排序vec
//按逆序排序:将最小元素放在vec的末尾
sort(vec.rbegin(), vec.rend());

反向迭代器需要递减运算符

不必惊讶,我们只能从既支持++也支持--的迭代器来定义反向迭代器。毕竟反向迭代器的目的是在序列中反向移动。

除了forward_list之外,标准容器上的其他迭代器都既支持递增运算又支持递减运算。

但是,流迭代器不支持递减运算,因为不可能在一个流中反向移动。

因此,不可能从一个forward_list或一个流迭代器创建反向选代器。

反向迭代器的注意点

假定有一个名为line的string,保存着一个逗号分隔的单词列表,我们希望打印line中的第一个单词。使用find可以很容易地完成这一任务:

//在一个逗号分隔的列表中查找第一个元素
auto comma =find(line.cbegin(), line.cend(),',');cout << string(line.cbegin(), comma) << endl;

如果line中有逗号,那么comma将指向这个逗号;否则,它将等于line.cend()。

当我们打印从line.cbegin()到comma之间的内容时,将打印到逗号为止的字符,或者行印整个string(如果其中不含逗号的话)。

如果希望打印最后一个单词,可以改用反向迭代器:

//在一个逗号分隔的列表中查找最后一个元素
auto rcomma = find(line,crbegin(), line.crend(),',');

由于我们将crbegin()和crend()传递给 find, find将从line的最后一个字符开始向前搜索。当find完成后,如果line中有逗号,则rcomma指向最后一个逗号——即,它指向反向搜索中找到的第一个逗号。如果 line 中没有逗号,则 rcomma 指向line.crend()。

当我们试图打印找到的单词时,最有意思的部分就来了。看起来下面的代码是显然的方法

    string line = "FIRST,MIDDLE,LAST";auto rcomma = find(line.crbegin(), line.crend(), ',');// 错误:将逆序输出单词的字符cout << string(line.crbegin(), rcomma) << endl;

但它会生成的输出结果和我们预期的不同哦。

这是因为我们使用的是反向迭代器,会反向处理string,因此,上述输出语句从 crbegin 开始反向打印line中内容。而我们希望按正常顺序打印从rcomma 开始到line末尾间的字符。所以我们不能直接使用rcomma。因为它是一个反向迭代器,意味着它会反向朝着string的开始位置移动。

那我们怎么按正常顺序打印最后一个单词呢?

需要做的是,将rcomma转换回一个普通迭代器,能在line 中正向移动。我们通过调用reverse_iterator的base成员函数来完成这一转换,此成员函数会返回其对应的普通迭代器

   string line = "FIRST,MIDDLE,LAST";auto rcomma = find(line. crbegin(), line.crend(),',');//正确:得到一个正向选代器,从逗号开始读取字符直到line末尾cout << string(rcomma. base(), line.cend()) << endl;

给定和之前一样的输入,这条语句会如我们的预期打印出LAST。

反向迭代器和普通迭代器的关系

图10.2中的对象显示了普通迭代器与反向迭代器之间的关系。

例如,rcomma 和rcomma.base()指向不同的元素,line.crbegin和line.cend()也是如此。这些不同保证了元素范围无论是正向处理还是反向处理都是相同的。

从技术上讲,普通迭代器与反向迭代器的关系反映了左闭合区间的特性。

关键点在于[line.crbegin(),rcomma)和[rcomma.base(),line.cend())指向line中相同的元素范围。

为了实现这一点,rcomma和rcomma.base()必须生成相邻位置而不是相同位置,crbegin()和cend()也是如此。

反向迭代器的目的是表示元素范围,而这些范围是不对称的,这导致一个重要的结果:当我们从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素

begin成员和end成员的返回值

begin和end 操作生成指向容器中第一个元素和尾元素之后位置的迭代器。

这两个迭代器最常见的用途是形成一个包含容器中所有元素的迭代器范围。

begin和end有多个版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器:

list<string> a ={"Milton", "Shakespeare", "Austen"};
auto itl = a.begin(); // list<string>::iterator
auto it2 = a.rbegin(); // list<string>::reverse iterator
auto it3 = a.cbegin(); // list<string>::const iterator
auto it4 = a.crbegin();// list<string>::const reverse iterator

不以c开头的begin和end成员

不以c开头的函数都是被重载过的。

也就是说,实际上begin(),end(),rbegin(),rend()都有两个版本。

一个是const成员,返回容器的const iterator类型。

另一个是非常量成员,返回容器的iterator类型。

那它什么时候调用哪个呢?

不以c开头的begin和end运算符的返回类型取决于调用它的这个对象是否是常量

如果对象是常量,begin和end返回const_iterator,如果对象不是常量,begin和end返回iterator

如果对象是常量,rbegin和rend返回const_reverse_iterator,如果对象不是常量,rbegin和rend返回reverse_iterator.

vector<int> a;
const vector<int> cv;
auto it1=v.begin();
auto it2=cv.begin();
auto it3=v.rbegin();
auto it4=cv.rbegin();

我们可以看到it1的类型是vector<int>::iterator,it2的类型是vector<int>::const iterator 

it3的类型是vector<int>::reverse_iterator;it4的类型是vector<int>::const_reverse_iterator

rbegin、begin(),end和rend的情况类似。当我们对一个非常量对象调用这些成员时,得到的是返回iterator的版本。只有在对一个const对象调用这些函数时,才会得到一个const版本。 

以c开头的begin和end成员

以c开头的begin和end成员只有一种版本,它只会返回const_iterator类型

也就是说cbegin()和cend()只会返回const_iterator类型

crbegin()和crend()只会返回const_reverse_iterator类型。

我们看看

vector<int> v;
auto it5=v.cbegin();
auto it6=v.crbegin();

可以看到啊,it5是const_iterator类型,it6是const_reverse_iterator类型

crend(),crbegin(),cbegin(),cend()的情况类似。只会得到const_iterator.

与const指针和引用类似可以将一个普通的iterator转换为对应的const_iterator,但反之不行。

话不多说,我们直接看例子 

   vector<int> vec = { 1, 2, 3, 4, 5 };vector<int>::iterator a = vec.begin();vector<int>::const_iterator b = a;//这是可以的
vector<int> vec = { 1, 2, 3, 4, 5 };vector<int>::const_iterator a = vec.begin();vector<int>::iterator b = a;//这是不可以的

普通迭代器(正向迭代器)和反向迭代器的关系

反向迭代器背后的原理是依靠正向迭代器创建出来的,

也就是说正向迭代器支持的运算操作,反向迭代器也支持,只不过操作的效果是相反的

迭代器运算符 

在C++中,迭代器提供了一些运算符来对迭代器进行操作和访问容器中的元素。以下是常用的迭代器运算符:

  1. 解引用运算符(*):用于获取迭代器指向位置的元素值。例如,*it 表示获取迭代器 it 指向位置的元素值。

  2. 自增运算符(++):用于将迭代器向前移动一个位置。例如,++it表示将迭代器 it 向前移动一个位置。

  3. 自减运算符(--):用于将迭代器向后移动一个位置。例如,--it表示将迭代器 it 向后移动一个位置。

  4. 箭头运算符(->):用于获取迭代器指向位置的成员变量或成员函数。例如,it->member 表示获取迭代器 it 指向位置的成员变量或成员函数。

  5. 等于运算符(==)和不等于运算符(!=):用于比较两个迭代器是否指向同一个位置。例如,it1 == it2 表示判断迭代器 it1 和 it2 是否指向同一个位置。

这些是所有容器迭代器都支持的操作,其中有一个例外不符合公共接口特点——forward_list迭代器不支持递减运算符(--)。

迭代器的算术运算

这些只能用于string,vector,deque,array的迭代器,我们不能将它们用于其他任何容器类型的迭代器 

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

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

相关文章

数字电子技术笔记——组合逻辑功能

1.Adder&#xff08;加法器&#xff09; Half-Adder&#xff08;半加器&#xff09; Full-Adder&#xff08;全加器&#xff09; 74LS283(4-bit parallel adders) 74LS283 4-bit parallel adders 81 input 41 output carry look-ahead adder &#xff08;超前进位加法器&a…

牛客 NC266925 我不是大富翁(dp)

原题 首先记录这一道题的目的是提醒自己&#xff1a;动态规划的属性并不是只有 m a x max max&#xff0c; m i n min min 和 c o u n t count count&#xff0c;同时还有布尔类型的dp 这题不能考虑在距离的维度上思考&#xff0c;比如说看走几步走到哪里了&#xff0c;如果…

C++进阶之路---手把手带你学习AVL树

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#…

图像处理与视觉感知---期末复习重点(3)

文章目录 一、空间域和频率域二、傅里叶变换三、频率域图像增强 一、空间域和频率域 1. 空间域&#xff1a;即所说的像素域&#xff0c;在空间域的处理就是在像素级的处理&#xff0c;如在像素级的图像叠加。通过傅立叶变换后&#xff0c;得到的是图像的频谱&#xff0c;表示图…

【深度学习笔记】9_9 语义分割和数据集

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 9.9 语义分割和数据集 在前几节讨论的目标检测问题中&#xff0c;我们一直使用方形边界框来标注和预测图像中的目标。本节将探讨语义分…

深度学习基础知识之Atrous卷积(空洞卷积)

太久不看代码确实生疏了&#xff0c;盯着一堆不同的dilation&#xff0c;不知道有什么作用&#xff0c;论文中说是Atrous卷积&#xff0c;原来就是空洞卷积的意思。 Dilated/Atrous Convolution 空洞卷积&#xff08;膨胀卷积/扩张卷积&#xff09; 空洞卷积是一种不增加参数量…

Web 服务器-Tomcat

文章目录 Web服务器一、Tomcat简介二、基本使用三、在IDEA中创建Maven Web项目四、在IDEA中使用Tomcat Web服务器 一、Tomcat简介 二、基本使用 三、在IDEA中创建Maven Web项目 四、在IDEA中使用Tomcat

外包干了9天,技术退步明显。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;2018年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

【C#】.net core 6.0 使用第三方日志插件Log4net,日志输出到控制台或者文本文档

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…

关于原型的一些总结

猛然发现太久没去复习了&#xff0c;于是复习了一些知识&#xff0c;顺便冒个泡。本次主要总结的知识点关于原型&#xff0c;再文章后半部分有原型相关的题&#xff0c;感兴趣的可直接观看。 一、原型 1.什么是原型 简单理解&#xff0c;原型就是一个对象&#xff0c;通过原…

Linux基础 想学好Linux请看这篇文章 Linux操作指令大全

当涉及学习 Linux 时&#xff0c;了解其基本原理和核心概念是至关重要的。Linux 是一种开源操作系统&#xff0c;广泛应用于服务器、嵌入式系统以及个人计算机中。它的灵活性、稳定性和安全性使得它成为许多 IT 专业人士和开发人员的首选。 第一步&#xff1a;了解基本概念和特…

CC连接过程

1、CC线连接过程 DFP和UFP会实时监控CC1和CC2引脚的电压&#xff0c;来评估DFP和UFP是否都已经在位。同时DFP可以根据电压确定自己所能提供的电流的大小 2、连接过程 Source端使用一个MOS管去控制Vbus&#xff0c;初始状态下&#xff0c;FET为关闭状态&#xff0c;Vbus不通。S…

苍穹外卖问题记录(持续更新)

Day01_3.2.4前后端联调 1. 前端无法登录 &#xff08;1&#xff09;确保nginx服务器已经启动 &#xff08;2&#xff09;查看自己数据库的用户名和密码是否和老师的一样&#xff0c;不一样的话需要在application-dev.yml文件中把老师的用户名密码修改成自己的 老师的用户名…

单⽬相机成像过程_看这一篇就够了

单⽬相机成像过程:看这一篇就够了 附赠宝贵的全套自动驾驶学习资料&#xff1a; 资料链接 附赠宝贵的全套自动驾驶学习资料&#xff1a; 资料链接

开发反应式API

开发反应式API 开发反应式API1 使用SpringWebFlux1.1 Spring WebFlux 简介1.2 编写反应式控制器 2 定义函数式请求处理器3 测试反应式控制器3.1 测试 GET 请求3.2 测试 POST 请求3.3 使用实时服务器进行测试 4 反应式消费RESTAPI4.1 获取资源4.2 发送资源4.3 删除资源4.4 处理错…

107. 如何使用Docker以及Docker Compose部署Go Web应用

文章目录 一、为什么需要Docker&#xff1f;二、Docker部署示例1. 准备代码2. 创建Docker镜像3. 编写Dockerfile4. Dockerfile解析5. 构建镜像6. 通过镜像创建容器运行 三、分阶段构建示例四、附带其他文件的部署示例五、关联其他容器六、Docker Compose模式七、总结 本文将介绍…

49、东北大学、阿尔伯塔大学:MVS-GCN多视角脑区、具有先验脑结构学习的图模型[GCN六元理论识别所有EEG!]

本文由东北大学医学图像智能计算教育部重点实验室&#xff0c;加拿大阿尔伯塔大学于2022年1.19日发表于<Computers in Biology and Medicine> JCR\IF: Q1\7.7 Abstract&#xff1a; 目的:近年来&#xff0c;脑功能网络(FBN)已被用于神经系统疾病的分类&#xff0c;如自…

力扣题目训练(21)

2024年2月14日力扣题目训练 2024年2月14日力扣题目训练605. 种花问题617. 合并二叉树628. 三个数的最大乘积289. 生命游戏299. 猜数字游戏149. 直线上最多的点数 2024年2月14日力扣题目训练 2024年2月14日第二十一天编程训练&#xff0c;今天主要是进行一些题训练&#xff0c;…

【智能算法】人工水母搜索算法(JS)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.代码实现4.参考文献 1.背景 2020年&#xff0c;Chou 等人受到水母运动行为启发&#xff0c;提出了人工水母搜索算法(Artificial Jellyfish Search Optimizer, JS)。 2.算法原理 2.1算法思想 JS模拟了水母的搜索行为&#xf…

关于OPC-UA客户端调用服务端方法CallMethod节点的问题

在OpcUaClient中可以通过CallMethodByNodeId调用方法节点 //// 摘要:// call a server method//// 参数:// tagParent:// 方法的父节点tag//// tag:// 方法的节点tag//// args:// 传递的参数//// 返回结果:// 输出的结果值public object[] CallMetho…