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

  • 32
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梆梆显眼梆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值