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

news2025/4/12 4:43:03

简介

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

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.coloradmin.cn/o/1520857.html

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

相关文章

谷歌浏览器运行vue项目。 控制台打印cjs.js,如何解决

当浏览器运行vue项目。 控制台打印cjs.js&#xff0c;如何解决 &#xff1a;

oracle基础-子查询 备份

一、什么是子查询 子查询是在SQL语句内的另外一条select语句&#xff0c;也被称为内查询活着内select语句。在select、insert、update、delete命令中允许是一个表达式的地方都可以包含子查询&#xff0c;子查询也可以包含在另一个子查询中。 【例1.1】在Scott模式下&#xff0…

Java项目企业设备管理系统

java项目企业设备管理系统javaweb项目ssm框架项目 运行环境:idea/eclipse tomcat jdk mysql navicat 系统用户分为员工和管理员两类用户。两类用户都可以进行系统的登录&#xff0c;虽然进入的系统主页结构相似&#xff0c;但是在功能上有不同。员工的密码可以自己进入系统后…

DirectShowPlayerService::doSetUrlSource: Unresolved error code 0x800c000d

报出这个问题&#xff0c;应该是对给的url解析不正确&#xff0c;我给的是rtsp的视频流地址&#xff0c;应该是对该格式解析异常。 所以参考两篇文&#xff1a; QT无法播放视频&#xff1a;报错&#xff1a;DirectShowPlayerService::doRender: Unresolved error code 0x8004…

2024年3月16日云仓酒庄广西发布会圆满举行

原标题&#xff1a;云仓酒庄广西发布会圆满举行&#xff0c;致敬经销商团队共谋未来发展 2024年3月16日&#xff0c;备受瞩目的云仓酒庄广西发布会在广西南宁隆重举行。此次发布会旨在感谢广西地区经销商团队的长期支持&#xff0c;并共同推进未来发展蓝图。活动现场氛围热烈&…

总要有一次,为自己疯狂

机会其实不多 最近一口气看了《飞驰人生》以及《飞驰人生2》&#xff0c;过去是以看喜剧的心态去看沈腾的电影&#xff0c;当如今二刷时发现这不就是生活吗&#xff0c;只不过用喜剧的外壳做了层包装。两部电影给我影响最深的就是最后的那段对白&#xff0c;“张弛&#xff0c…

【大模型系列】统一图文理解与生成(BLIP/BLIPv2/InstructBLIP)

文章目录 1 BLIP(2022, Salesforce Research)1.1 简介1.2 数据角度1.3 模型角度1.4 BLIP预训练的目标 2 BLIP2(ICML2023, Salesforce)2.1 简介2.2 模型架构2.3 训练细节 3 InstructBLIP(2023, Salesforce)3.1 指令微调技术(Instruction-tuning)3.2 数据集准备3.3 Instruction-a…

数据结构——动态顺序表

数据结构的动态顺序表有以下几个操作&#xff1a;创建&#xff0c;销毁&#xff0c;初始化&#xff0c;增删查改和打印以及内存空间不够时的扩容 本文的宏定义&#xff1a; #define SeqTypeData int 1.动态顺序表的创建 typedef struct SeqListInit{//动态顺序表的创建SeqT…

双指针算法_复写零

题目&#xff1a; 给一个固定长度的数组arr&#xff0c;将数组中出现的每一个0都复写一遍&#xff0c;并且将其余元素都往右移动 且不要再超过数组长度的位置写入元素&#xff0c;在数组上直接修改 示例&#xff1a; 双数组模拟操作&#xff1a; 从示例来看&#xff0c;因为…

【状态压缩DP】第十三届蓝桥杯省赛C++ B组《积木画》(C++)

【题目描述】 小明最近迷上了积木画&#xff0c;有这么两种类型的积木&#xff0c;分别为 I 型&#xff08;大小为 2 个单位面积&#xff09;和 L 型&#xff08;大小为 3 个单位面积&#xff09;&#xff1a; 同时&#xff0c;小明有一块面积大小为 2N 的画布&#xff0c;画布…

模板进阶:非类型模板参数,特化

一、非类型模板参数 非类型模板参数&#xff0c;就是用一个常量作为 类/函数 的模板参数&#xff0c;在 类/函数 中可以被当成常量使用。 template<class T, size_t N>// N 为一个非类型模板参数 class Stack { public:Stack(){_a new T[N];} protected:T* _a; };int m…

腾讯云服务器入站规则端口开放使用指南(CentOS系统)

第一步&#xff1a;开放安全组入站规则 来源处0.0.0.0/0是对IPv4开发&#xff0c;::/0是对IPv6开放&#xff1b; 协议端口按照提示填写即可。云服务器防火墙开放 第三步&#xff1a;本地防火墙开放 sudo firewall-cmd --zonepublic --add-port你的端口号/tcp --perma…

RTT——stm32f103的can总线通信

1.创建工程 2.配置时钟和引脚 引脚配置使能CAN 时钟配置&#xff0c;采用外部高速时钟 生成MDK工程后复制相关初始化函数到RTT-studio中 将void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)函数复制至broad.c文件中 将时钟配置函数复制到drv_clk.c中&#xff0c;只复制函数…

Java8中Stream流API最佳实践Lambda表达式使用示例

文章目录 一、创建流二、中间操作和收集操作筛选 filter去重distinct截取跳过映射合并多个流是否匹配任一元素&#xff1a;anyMatch是否匹配所有元素&#xff1a;allMatch是否未匹配所有元素&#xff1a;noneMatch获取任一元素findAny获取第一个元素findFirst归约数值流的使用中…

Linux下的多线程编程:原理、工具及应用(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Flower of Life—陽花 0:34━━━━━━️&#x1f49f;──────── 4:46 &#x1f504; ◀️ ⏸ ▶️ ☰ …

【C++ RB树】

文章目录 红黑树红黑树的概念红黑树的性质红黑树节点的定义红黑树的插入代码实现总结 红黑树 AVL树是一颗绝对平衡的二叉搜索树&#xff0c;要求每个节点的左右高度差的绝对值不超过1&#xff0c;这样保证查询时的高效时间复杂度O( l o g 2 N ) log_2 N) log2​N)&#xff0c;…

钉钉小程序 - - - - - 如何通过一个链接打开小程序内的指定页面

方式1 钉钉小程序 scheme dingtalk://dingtalkclient/action/open_mini_app?miniAppId123&pagepages%2Findex%2Findex%3Fx%3D%25E4%25B8%25AD%25E6%2596%2587 方式2 https://applink.dingtalk.com/action/open_mini_app?type2&miniAppIdminiAppId&corpIdcorpId&…

MySQL行锁核心知识介绍

MySQL的行锁是数据库中用于控制并发访问的一种机制。它允许在数据库的行级别上实现锁定&#xff0c;从而允许多个事务同时修改不同行的数据&#xff0c;而不会相互干扰。这种锁机制可以提高数据库的并发性能&#xff0c;减少锁争用&#xff0c;提高事务的吞吐量。在本教程中&am…

一周学会Django5 Python Web开发-Jinja3模版引擎-安装与配置

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计35条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

.Net Core 中间件验签

文章目录 为什么是用中间件而不是筛选器&#xff1f;代码实现技术要点context.Request.EnableBuffering()指针问题 小结 为什么是用中间件而不是筛选器&#xff1f; 为什么要用中间件验签&#xff0c;而不是筛选器去验签? 1、根据上图我们可以看到&#xff0c;中间件在筛选器之…