掘根宝典之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.mfbz.cn/a/459738.html

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

相关文章

10、MongoDB -- MongoDB 的 MongoTemplate 的功能和用法介绍

目录 MongoTemplate 的功能和用法演示前提&#xff1a;登录单机模式的 mongodb 服务器命令登录【test】数据库的 mongodb 客户端命令登录【admin】数据库的 mongodb 客户端命令 为 MongoDB 提供的两个 Starterspring-boot-starter-data-mongodb&#xff08;为以同步方式操作 Mo…

Jmeter —— jmeter对图片验证码的处理!

jmeter对图片验证码的处理 在web端的登录接口经常会有图片验证码的输入&#xff0c;而且每次登录时图片验证码都是随机的&#xff1b;当通过jmeter做接口登录的时候要对图片验证码进行识别出图片中的字段&#xff0c;然后再登录接口中使用&#xff1b; 通过jmeter对图片验证码…

第N4周:中文文本分类-Pytorch实现

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/rbOOmire8OocQ90QM78DRA) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制](https://mtyjkh.blog.csdn.net/)** # -*- coding: utf-8 -…

IDEA编译安卓源码TVBox(2)

一、项目结构&#xff1a;主要app和player app结构 二、增加遥控器按键选台 修改LivePlayActivity.java 1、声明变量 public String channelId "";public Timer timer new Timer();public Toast mToast;2、定义方法 private void mToastShow(String s){mToast …

攻防世界-misc-Make-similar

题目链接&#xff1a;攻防世界 (xctf.org.cn) 下载得到ogg文件。Olympic CTF 2014原题有提示120 LPM&#xff0c;对应Radiofax。需要将ogg格式文件转换成wav格式音频后&#xff0c;用OS X下的软件Multimode转换成单色传真图像&#xff1a; 文字部分为&#xff1a; section 1 of…

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

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

PlayBook 详解

4&#xff09;Playbook 4.1&#xff09;Playbook 介绍 PlayBook 与 ad-hoc 相比&#xff0c;是一种完全不同的运用 Ansible 的方式&#xff0c;类似与 Saltstack 的 state 状态文件。ad-hoc 无法持久使用&#xff0c;PlayBook 可以持久使用。 PlayBook 剧本是 由一个或多个 “…

5分钟上手Python爬虫:从干饭开始,轻松掌握技巧

很多人都听说过爬虫&#xff0c;我也不例外。曾看到别人编写的爬虫代码&#xff0c;虽然没有深入研究&#xff0c;但感觉非常强大。因此&#xff0c;今天我决定从零开始&#xff0c;花费仅5分钟学习入门爬虫技术&#xff0c;以后只需轻轻一爬就能查看所有感兴趣的网站内容。广告…

Docker 安装部署 ORACLE 11g数据库

Docker 安装部署 ORACLE 11g数据库 背景&#xff1a; ​ 最新在开发数据中台数据接入模块&#xff0c;其中设计很多数据类型&#xff0c;包括ORACLE &#xff0c;因为是测试使用&#xff0c;想着快速部署测试&#xff0c;于是使用Docker 部署 Oracle , 生产环境不建议使用Doc…

【LeetCode热题100】160. 相交链表(链表)

一.题目要求 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数…

鸿蒙Next学习-Flex布局

Entry Component struct FlexCase {build() {//需要在构造参数上传Flex({ direction: FlexDirection.Row,justifyContent:FlexAlign.Center }) {//flex布局Row().width(100).height(100).backgroundColor(Color.Red)Row().width(100).height(100).backgroundColor(Color.Yellow…

【ubuntu】安装 Anaconda3

目录 一、Anaconda 说明 二、操作记录 2.1 下载安装包 2.1.1 官网下载 2.1.2 镜像下载 2.2 安装 2.2.1 安装必要的依赖包 2.2.2 正式安装 shell 和 base 的切换 2.2.3 检测是否安装成功 方法一 方法二 方法三 2.3 其他 三、参考资料 3.1 安装资料 3.2 验证是否…

C语言函数—递归理解和练习

练习&#xff1a; 编写函数不允许创建临时变量&#xff0c;求字符串的长度。 我们看到这道题&#xff0c;第一个想到的是不是strlen int main() {char[] "bit";//[b][i][t][\0]//里面一共4个字符&#xff08;包括结尾的、0&#xff09;但是我们的strlen函数并不会计…

Navicat破解 Navicat下载安装 附教程 免费

百度网盘&#xff1a;https://pan.baidu.com/s/1wRRN_18_uXxPiIWCS4l43A 麻烦各位师傅帮忙填写一下问卷&#xff0c;提取码在问卷填写结束后显示~ 【https://www.wjx.cn/vm/mBBTTKm.aspx# 】 &#xff08;资料来源于网络&#xff0c;侵告删&#xff09;

‘sqlcmd‘不是内部或外部命令,也不是可运行的程序或批处理文件。

目录 一、问题 二、下载&安装sqlcmd 实用工具 三、验证 四、结果 一、问题 今天使用批处理文件执行SQLServer数据库的SQL语法时报错&#xff0c;提示sqlcmd不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。&#xff0c;发生这个问题的原因是当前系统缺少…

程序员必备开发工具、程序员必备集成开发环境(IDE)

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

大型政企寻求“智能化配方”,谁是“偏方”,谁是“验方”?

文 | 智能相对论 作者 | 叶远风 两会落幕&#xff0c;“人工智能”已成为国策&#xff0c;而全面推进智能化建设&#xff0c;大型政企首当其冲、责无旁贷——它们既是智能化转型升级的重要构成部分&#xff0c;也能直接在垂直领域形成价值引领、以点带片。 当智能成为大型政…

常用电路学习——开关机电路

开关机电路 最近在学习的时候&#xff0c;碰到了开关机电路&#xff0c;当时不懂R16的作用&#xff0c;一路磕磕碰碰&#xff0c;终于弄懂了&#xff0c;是上拉电阻&#xff0c;现在分享以下学习经验 。 软件开关机电路 IO1是单片机输出口&#xff0c;上电后默认输出1。 IO2…

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

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

PTA题解 --- N个数求和(C语言)

今天是PTA题库解法讲解的第二天&#xff0c;今天我们要讲解N个数求和&#xff0c;题目如下&#xff1a; 要解决这个问题&#xff0c;我们可以用C语言编写一个程序来处理和简化分数。程序的基本思路如下&#xff1a; 1. 定义一个函数来计算两个数的最大公约数&#xff08;GCD&a…
最新文章