掘根宝典之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的迭代器,我们不能将它们用于其他任何容器类型的迭代器 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

后端系统开发之——创建注册接口

原文地址&#xff1a;后端系统开发之——创建注册接口 - Pleasure的博客 下面是正文内容&#xff1a; 前言 这是一篇SpringBoot项目的实践篇。 主要用于介绍如何从零开始搭建某一种类型的系统。 个人认为&#xff0c;只要后端逻辑完善了&#xff0c;纵使前端页面千变万化都可…

硬件基础:带缓启动MOS管电源开关电路

电源开关电路&#xff0c;经常用在各“功能模块”电路的电源通断控制&#xff0c;是常用电路之一。 本文要讲解的电源开关电路&#xff0c;是用MOS管实现的&#xff0c;且带缓开启功能&#xff0c;非常经典。 一、电路说明 电源开关电路&#xff0c;尤其是MOS管电源开关电路…

2024-3-18-C++day6作业

1>思维导图 2>试编程 要求: 封装一个动物的基类&#xff0c;类中有私有成员&#xff1a;姓名&#xff0c;颜色&#xff0c;指针成员年纪 再封装一个狗这样类&#xff0c;共有继承于动物类&#xff0c;自己拓展的私有成员有&#xff1a;指针成员&#xff1a;腿的个数&a…

蓝桥杯-24点-搜索

题目 思路 --暴力递归全组合的方法。只有4个数&#xff0c;4种计算方式&#xff0c;共有4 * 3 * 2 * 1 * 4种不同的情况&#xff0c;可以写递归来实现。 --每次计算都是两个数之间的运算&#xff0c;因此4个数需要3次计算&#xff0c;第一次计算前有4个数&#xff0c;第二次有…

Mysql与MyBatis

1 Sql语句 增删改查 1.1 建表 -- cmd展示数据库 show databases ; -- cmd登录数据库 mysql localhost -u root -p-- auto_increment 自动增长&#xff0c;每添加一个表项id自动增1 -- char定长字符串 0-255&#xff0c;不足十个字符按十个字符算&#xff0c; varchar变长字符串…

阿里云服务器“地域”,这么选择就对了!

阿里云服务器地域选择方法&#xff0c;如何选择速度更快、网络延迟更低的地域节点&#xff0c;地域指云服务器所在的地理位置区域&#xff0c;地域以城市划分&#xff0c;如北京、杭州、深圳及上海等&#xff0c;如何选择地域&#xff1f;建议根据用户所在地区就近选择地域&…

预防近视的台灯有哪些?多款专家说好的护眼台灯推荐

现在的儿童青少年近视率真的非常高&#xff01;据统计&#xff0c;我国儿童青少年的总体近视率为52.7%&#xff0c;其中6岁儿童为14.3%&#xff0c;小学生为35.6%&#xff0c;初中生为71.1%&#xff0c;高中生为80.5%。而造成如此高近视率的原因主要还是长时间过度用眼导致的疲…

【Qt问题】使用QSlider创建滑块小部件无法显示

问题描述&#xff1a; 使用QSlider创建滑块小部件用于音量按钮的时候&#xff0c;无法显示&#xff0c;很奇怪&#xff0c;怎么都不显示 一直是这个效果&#xff0c;运行都没问题&#xff0c;但是就是不出现。 一直解决不了&#xff0c;最后我在无意中&#xff0c;在主程序中…

LabVIEW飞行器螺旋桨性能测试与数据监控

LabVIEW飞行器螺旋桨性能测试与数据监控 开发LabVIEW的电动飞行器螺旋桨性能测试与数据监控系统&#xff0c;专门针对电动飞行器螺旋桨在运行过程中的性能测试和监控需求。通过采集转速、转矩、拉力和温度等关键参数&#xff0c;系统能够实时监测和分析螺旋桨的状态&#xff0…

AI - 集成学习

目录 集成学习概念 集成学习器性能评估 随机森林 AdaBoost &#x1f606;&#x1f606;&#x1f606;感谢大家的阅读&#x1f606;&#x1f606;&#x1f606; 集成学习概念 &#x1f48e;集成学习是机器学习中的一种思想&#xff0c;它通过多个模型的组合形成一个精度…

【Spring MVC】Spring MVC拦截器(Interceptor)

目录 一、拦截器介绍 二、拦截器 Interceptor 定义 2.1 HandlerInterceptor接口 2.2 Spring MVC中提供的一些HandlerInterceptor接口实现类 1、AsyncHandlerInterceptor 2、WebRequestInterceptor 3、MappedInterceptor 4、ConversionServiceExposingInterceptor 三、拦…

力扣题单(小白友好)

力扣题单 算法小白自用题单,目前对于一些简单的数据结构感觉掌握的还可以,但是力扣很多题还是需要看题解,不够熟练;故整理了一份题单,用于巩固练习; 网上确实有很多对于算法分类讲解的网站,but:有一丢丢选择困难症,每天不知道该刷什么题,再加上网站对于一类题一般就有十几道题目…

人工智能技术的不当利用与风险

目录 前言1 视频篡改技术的滥用1.1 虚假信息传播与社会动荡1.2 对公众信任的破坏与舆论混乱 2 隐私泄露与监视风险2.1 个人信息安全与数据滥用风险2.2 社会稳定与个人自由权利的平衡 3 虚假评论与信息传播3.1 舆论操纵与社会意识形态的影响3.2 对信息可信度与公众信任的威胁 结…

雷龙发展Nand flash芯片试用体验

一、项目背景 最近自己开始准备了一个智能家居控制系统项目,需要包含室内的温湿度、空气质量、烟雾浓度以及气体含量,能够存储相应的数据,并进行显示。 Nand-flash存储器是flash存储器的一种,其内部采用非线性宏单元模式,为固态大容量内存的实现提供了廉价有效的解决方案…

阿里云ecs服务器配置反向代理上传图片

本文所有软件地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/12OSFilS-HNsHeXTOM47iaA 提取码&#xff1a;dqph 为什么要使用阿里云服务器&#xff1f; 项目想让别人通过外网进行访问就需要部署到我们的服务器当中 1.国内知名的服务器介绍 国内比较知名的一些…

一招让你的Mac重获新生!CleanMyMac专业软件助你清理系统垃圾,保障安全!

一、Mac用户必备神器——CleanMyMac 随着Mac的使用时间越来越长&#xff0c;系统中的垃圾文件也会越来越多&#xff0c;不仅占用宝贵的硬盘空间&#xff0c;还会影响系统的运行速度。这时候&#xff0c;一款专业的清理软件就显得尤为重要。CleanMyMac作为荣获多项大奖的清理软件…

录视频的软件推荐,助力视频内容创作

随着网络技术的发展和在线教育的兴起&#xff0c;录制视频教程的需求日益增加。无论是制作教学课程、分享办公技巧&#xff0c;还是录制游戏过程&#xff0c;一款好用的录屏软件都至关重要。本文将深入介绍三款录视频的软件&#xff0c;帮助读者了解它们的特点和操作步骤&#…

【Linux】回顾 C 文件接口

文章目录 1. 写文件2. 读文件3. 输出信息到显示器的几个方法4. stdin / stdout / stderr5. 打开文件的方式 1. 写文件 int main() {FILE* fp fopen("myfile", "w");if (!fp){printf("fopen error!\n");}const char* msg "hello world!\n…

自己录的视频怎么配上字幕?推荐几种方法

自己录的视频怎么配上字幕&#xff1f;在数字化时代&#xff0c;视频已经成为人们获取信息、娱乐消遣的重要形式。而对于许多内容创作者来说&#xff0c;为自己的视频添加字幕不仅能提升观众的观看体验&#xff0c;还能增加视频的专业度和吸引力。那么&#xff0c;如何为自己的…

初次文件包含漏洞

1.文件包含漏洞介绍 1.1.文件包含漏洞解释   文件包含漏洞就是使用函数去包含任意文件的时候&#xff0c;当包含的文件来源过滤不严谨的时候&#xff0c;当存在包含恶意文件后&#xff0c;就可以通过这个恶意的文件来达到相应的目的。 1.2.文件包含漏洞原理    其实原理就…