C++基本语言:1.9迭代器精彩演绎,失效分析及弥补、实战

2024-01-16 18:40

本文主要是介绍C++基本语言:1.9迭代器精彩演绎,失效分析及弥补、实战,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C++基本语言包含10章节内容,存于C++从入门到精通专栏

目录

一、迭代器简介

二、容器的迭代器类型

三、迭代器begin()/end()、反向迭代器rbegin/rend操作

1.迭代器

1.1begin和end

1.2 反向迭代器 rbegin()和rend()

四、迭代器运算符

五、迭代器运算符 const_iterator迭代器(只读)

cbegin和cend

六、迭代器失效

6.1添加元素和从容器中删除元素操作要小心

6.2不同的容器实现机理不同

6.3灾难程序演示

灾难程序演示1

灾难程序演示2

七、范例演示

1)用迭代器遍历string类型数据

2)vector容器常用操作与内存释放

一、迭代器简介

vector是一个容器,里面可以容纳很多对象。

迭代器是一种遍历容器内元素的 数据类型。

这种数据类型感觉有点像指针,读者就理解为迭代器是用来指向容器中的 某个元素 的。

可以通过“[]”(下标)访问string字符串中的字符、访问vector中的元素。

但实际上,在C++中,很少通过下标来访问它们,一般都是采用迭代器来访问(更通用)

因为除了vector容器外,C++标准库中还有几个其他种类的容器。这些容器都可以使用迭代器来遍历其中的元素内容

string其实是字符串,不属于容器,但string也支持用迭代器遍历。

通过迭代器,可以读取容器中的元素值、修改容器中某个迭代器所指向的元素值。

此外,迭代器可以像指针一样,通过++、--等 运算符从容器中的一个元素移动到另一个元素。

在C++标准库中,还有其他容器如list、 map等都属于比较常用的容器,C++标准库为每个这些容器都定义了对应的一种迭代器类型,有很多容器不支持“[]”操作,但容器都支持迭代器操作。

结论:尽量用迭代器来访问容器中的元素。  

二、容器的迭代器类型

C++标准库为每种容器都定义了对应的迭代器类型。这里就以容器vector为例

7816d02911d34b96b10996aa826764e2.png这个变量的类型是vector<int>::iterator类型

“::iterator”是每个容器(如vector)里面都定义了的一个成员(类型名),这个名字是固定的,请牢记。

在理解的时候,就把整个vector<int>::iterator理解成一种类型, 这种类型就专门应用于迭代器,当用这个类型定义一个变量的时候iter,这个变量就是一个迭代器。

三、迭代器begin()/end()、反向迭代器rbegin/rend操作

1.迭代器

每一种容器,如vector,都定义了begin和end的成员函数

1.1begin和end

这两个成员函数正好用来返回迭代器类型。

(1)begin返回一个迭代器类型(就理解成返回一个迭代器)

8b53c91648894506bfaf8a3090c8cc59.png

791efa543be343039c03c6e1f8650bb4.png

(2)end返回一个迭代器类型(就理解成返回一个迭代器)

f614e14631604e63a8a01c3d39fc8b1a.png

对上面的代码进行跟踪调试,观察begin和end结果可以看到, end()指向了一个乱数字

e29f190ee5d14c4d91020941cb906c6f.png

(3)如果容器为空,则begin返回的迭代器和end返回的迭代器相同。

1e88e5e31aac4df0a4f4157252a44569.png

结论:end返回的迭代器并不指向容器vector中的任何元素,它起到实际上是一个标志(岗哨)作用,如果迭代器从容器的begin位置开始不断往后游走,也就是不断遍历容器中的元素,那么如果有一个时刻,iter 走到了end位置,那就表示已经遍历完了容器中的所有元素。

(4)写一段代码,传统的通过迭代器访问容器中元素的方法(100,200,300)

99ef65a842d74f8c803cde1a3b114f41.png

1.2 反向迭代器 rbegin()和rend()

想从后面往前遍历一个容器,那么反向迭代器就比较方便。

反向迭代器使用的是rbegin成员函数和rend成员函数。

 1)rbegin返回一个反向迭代器类型,指向容器的最后一个元素。

 2)rend返回一个反向迭代器类型,指向容器的第一个元素的前面位置。

025ce6d6a32c4788ad6e227f89b93fc3.png

范例:运行起来看结果:300、200、100

eefc6948ce174e0da7b54f9eebb05c71.png

四、迭代器运算符

(1)*iter:返回迭代器iter所指向元素的引用。(类似于*p)

必须要保证该迭代器指向的是有效的容器元素,不能指向end,因为end是末端元素后面的位置 ,也就是说,end 已 经指向了一个不存在元素 。

(2)++iter/iter++:让迭代器指向容器中的下一个元素。

但是已经指向end的迭代器,不能再++,否则运行时报错。

(3)--iter 和 iter--: 让迭代器指向容器中的前一个元素。

但是已经指向begin的迭代器,不能再--,否则运行时报错。

38cf1b41d1904a848b2b15e58312cf05.png

5638d00474fc4addb9717a5d8da18cd4.png

(4)iter1==iter2或iter1!=iter2:判断两个迭代器是否相等。如果两个迭代器指向的是同一个元素,就相等,否则就不相等。

(5)如何引用结构中的成员

5b299b1d2c93467bb86c5edadf1316c3.png

请注意:一定要确保迭代器指向有效的容器中的元素,否则范例中的 这些行为可能会导致意想不到的结果。

还有很多其他运算符,例如迭代器之间可以相减表示两个迭代器之间 的距离,迭代器加一个数字表示跳过多少个元素,不过这些都不常用,不准备逐一介绍,意义也不大。

五、迭代器运算符 const_iterator迭代器(只读)

实际上每种容器还有另外一种迭代器类型,叫作const_iterator

含义:有const 在,一般都表示常量,也就是说值不能改变的意思。

这里的值不能改变表示该迭代器指向的元素的值不能改变,并不表示该迭代器本身不能改变。也就是说,该迭代器是可以不断地指向容器中的下一个元素的。

terator只能从容器中读元素,不能通过该迭代器修改容器中的元素。

所以说,const_iterator更像一个常量指针,而 iterator迭代器是能读能写的。

357f654458944c058147ce3fcb36692a.png

什么时候用const_iterator呢?

如果这个容器对象是一个常量,那么就必须使用const_iterator,否则报错:

25429fd6b96248c59d4964a2e6fc5943.png

cbegin和cend

C++11引入的两个 新函数cbegin和cend成员函数,与begin、end非常类似。

不管容器是否是常量容器,cbegin、cend返回的都是常量迭代器const_iterator。

f58e6a3634b34d29ab8b9df066afda00.png

六、迭代器失效

6.1添加元素和从容器中删除元素操作要小心

如果在for循环中,通过push_back等手段往容器中增加元素,范围 for循环输出的容器中元素就会混乱,要么有结果但混乱,要么直接崩溃。

其实,范围for语句等价于常规的用迭代器对容器进行操作。

72bbac29a5bd4a8da8dd5951b46a4ea3.png

等价于迭代器这种操作方式:

ec5f147da52f47618832b5bbb870057e.png

一旦在for循环中增删容器中的元素,就会导致迭代器失效, 整个结果就混乱了。

     其实,任何一种能够改变vector对象容量的操作,如push_back, 都会使当前的vector对象迭代器失效,请读者谨记:在操作迭代器的过程中(使用了迭代器的这种循环体)

千万不要改变vector对象的容量,也就是不要增加或者删除vector容器中的元素。

94131d18bdd74b488f6e724a27f5505a.png

对于向容器中添加元素和从容器中删除元素操作要小心,因为这些操作可能都会使指向容器元素的迭代器(也包括指针、引用等)失效。

失效就表示它不能再代表任何容器中的元素,一旦使用这种失效的迭代器,就表示程序的书写犯了严重错误,很多情况下都会导致程序崩溃,就好比使用了没有被初始化的指针一样。

6.2不同的容器实现机理不同

(例如有的容器内部数据是连续存储的,插入元素时一旦原有内存不够用,则可能就会导致容器中原有数据全部迁移到一个新内存去,如vector等容器),不同的插入操作、不同的插入位 置,会导致迭代器、指针、引用部分或者全部失效。

甚至在循环体中的诸如vecvalue.end()代码都会因为插入数据操作导致失效。

另一种情况是删除操作。如果从容器中删除一个元素,那么,当前指向这个被删除元素的迭代器、指针、引用肯定是立即失效,绝不能再引用它们。 此外,不同的容器,针对删除操作,不同的删除位置,也会导致迭代器 、 指 针 、 引用部分或者全部失效 , 甚 至 在 循 环 体 中 的 诸 如 vecvalue.end()代码都会因为删除数据操作导致失效。

解决方法就是:

如果在一个使用了迭代器的循环中插入元素到容器, 那只插入一个元素后就应该立刻跳出循环体,不能再继续用这些迭代器操作容器。

8a4845f4b9ed4467acd463b2c9793b7c.png

效果:

76a4c486ceff4defb7ddbf3be51c9596.png

6.3灾难程序演示

灾难程序演示1

下面代码目前一切没有问题:

180de23f225e459a9180515e74aa5697.png

接着,往循环中增加代码,注意while循环体中代码的变化:

d04694791c7c449aa52b973c1915ed10.png

如果有如下需求:

想不断地插入多条数据,并且还希望迭代器不失效,那就得查资料研究,如研究针对vector容器,如何写 insert这段代码,才能让迭代器不失效,让程序安全地运行。看如下代 码,是一种满足连续插入多条数据的解决方案:

相当于每次更新end,防止end失效

insert后返回的还是迭代器,指向(icount+80)

7df68d13852f4b7395079e00769135f8.png

*beg的值依次是:80,81……90

太细节的东西就不过多涉及,迭代器会失效的道理读者都懂了。

一般的做法就是如果这个循环和迭代器有关,基本都只会做插入或删除操作一次,然后会立即break,因为保不准插入或删除操作导致哪个迭代器失效,所以立即break到循环体到外面去。

灾难程序演示2

在一个程序运行结束之前,可能会习惯性地释放掉vector里面的内容。

有些人可能会写出这样的代码来在程序的最后进行释放处理:

789d0f0d28864e4787bb9c623ab14782.png

运行后程序崩溃。肯定是迭代器失效导致崩溃。那就一定得要小心这种释放代码。

经过分析,erase会返回下一个元素位置,这个位置肯定要想办法保存,但因为这里用的是for循环,for循环里每次还有++iter这种操作,所 以怎样改造能够让它安全释放,这是一个问题。经过思考和研究,找到了 一种写法能够让容器顺利释放:

8d66991d7d574df283447500d7288031.png

所以,如果说要把容器一下全部清空,用clear还是其他方法也好, 都还算简单。

可以使用 iv.clear();

但是如果需要用和迭代相关的循环来一个元素一个元素地删除,那一定更要注意。

这里笔者推荐一个简单直接且有效的方法:

d7f761d099804cf4be7f7c0c65dccbee.png

七、范例演示

1)用迭代器遍历string类型数据

563d636ceb4945248ddb8c083b26626c.png

2)vector容器常用操作与内存释放

实践程序:

假设有一些配置项,配置项里记录一些配置数据,当然这些配置项正常来讲应该写在文件中,这样方便随时修改,但因为是演示目的,就写在代码中即可。

a951db8ffc3e48d9a559068386896fdc.png

然后是主函数

e141368a43b14c7888a9b2fe248bc3b4.png

6222898c32104b25a2b021b9b3b99320.png

这里用到了一个函数

41b0ab123685466eba9dee3b27dca080.png

看一看conflist容器结构示意图

4729330ccec0431189bcae173824111c.png

902fe782d6f8494eb41b37101c784d16.png

这篇关于C++基本语言:1.9迭代器精彩演绎,失效分析及弥补、实战的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/613538

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

基本知识点

1、c++的输入加上ios::sync_with_stdio(false);  等价于 c的输入,读取速度会加快(但是在字符串的题里面和容易出现问题) 2、lower_bound()和upper_bound() iterator lower_bound( const key_type &key ): 返回一个迭代器,指向键值>= key的第一个元素。 iterator upper_bou

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl