STL序列式容器中删除元素的方法和陷阱一

英飞凌Tricore实战系列导读 笔者结合项目应用经验对Tricore MCU的开发及应用经验进一步总结,成人达己,渡己渡人。 阅读详情

在 STL (标准模板库)中经常会碰到要删除容器中部分元素的情况,本人在编程中就经常编写这方面的代码,在编码和测试过程中发现在 STL 中删除容器有很多陷阱,网上也有不少网友提到如何在 STL 中安全删除元素这些问题。本文将讨论编程过程中最经常使用的两个序列式容器 vector 、 list 中安全删除元素的方法和应该注意的问题,        其它如 queue 、 stack 等配接器容器( container adapter ),由于它们有专属的操作行为,没有迭代器( iterator ),不能采用本文介绍的删除方法,至于 deque ,它与 vector 的删除方法一样。 STL 容器功能强大, but no siliver bullet ,如果你使用不当,也将让你吃尽苦头。

1 .手工编写 for 循环代码删除 STL 序列式容器中元素的方法

例如,你能看出以下代码有什么问题?

例 1 :

#include <iostream>

#include <vector>

using namespace std;

void main( ) {

       vector<int> vectInt;

       int i;

       //     初始化 vector 容器

       for (i = 0; i < 5; i++ ) {

              vectInt.push_back( i );

       }

       //     以下代码是要删除所有值为 4 的元素

       vector<int>::iterator itVect = vectInt.begin();

       for ( ; itVect != vectInt.end();  ++itVect ) {

              if ( *itVect == 4 ) {

                     vectInt.erase( itVect );

              }

       }

       int iSize = vectInt.size();

      for (  i = 0 ; i < iSize; i++ )  {

                     cout << " i= " << i <<  ", " << vectInt[ i ] << endl;

       }

 

}

例 2 :

#include <iostream>

#include <vector>

using namespace std;

void main( ) {

       vector<int> vectInt;

       int i;

       //     初始化 vector 容器

       for ( i = 0; i < 5; i++ ) {

              vectInt.push_back( i );

              if ( 3 == i ) {

                     //       使 3 的元素有两个,并且相临。这非常关键,否则将发现不了 bug 。

                     //   具体解释见下。

                     vectInt.push_back( i );

              }

       }

       vector<int>::iterator itVect = vectInt.begin();

       vector<int>::iterator itVectEnd = vectInt.end(); //       防止 for 多重计算

       //     以下代码是要删除所有值为 3 的元素

       for ( ; itVect != itVectEnd; ++itVect ) {

              if ( *itVect == 3 ) {

                     itVect = vectInt.erase( itVect );

              }

       }

       int iSize = vectInt.size();

      for (  i = 0 ; i < iSize; i++ )  {

                     cout << " i= " << i <<  ", " << vectInt[ i ] << endl;

       }

例 3 :

#include <iostream>

#include <vector>

using namespace std;

void main( ) {

       vector<int> vectInt( 5 );

       int i;

       vectInt[ 0 ] = 0;

       vectInt[ 1 ] = 1;

       vectInt[ 2 ] = 2;

       vectInt[ 3 ] = 3;

       vectInt[ 4 ] = 4; //     替换为 vectInt[ 4 ] = 3; 试试

       vector<int>::iterator itVect = vectInt.begin();

       vector<int>::iterator itVectEnd = vectInt.end(); //       防止 for 多重计算

       //     以下代码是要删除所有值为 3 的元素

       for ( ; itVect != itVectEnd; ) {

              if ( *itVect == 3 ) {

                     itVect = vectInt.erase( itVect );

              }

              else {

                     ++itVect;

              }

       }

       int iSize = vectInt.size();

      for (  i = 0 ; i < iSize; i++ )  {

                     cout << " i= " << i <<  ", " << vectInt[ i ] << endl;

       }

}

 

分析:

这里最重要的是要理解 erase 成员函数,它删除了 itVect 迭代器指向的元素,并且返回要被删除的 itVect 之后的迭代器, 迭代器相当于一个智能指针,指向容器中的元素,现在删除了这个元素,将导致内存重新分配,相应指向这个元素的迭代器之后的迭代器 就失效了,但 erase 成员函数返回要被删除的 itVect 之后的迭代器 。

例 1 将导致程序未定义的错误,在 windows 中即是访问非法内存,程序当掉。因为 vectInt.erase( itVect ); 调用后 itVect 之后的迭代器已无效了,所以当执行 ++itVect 后, *itVect 访问了非法内存。例 1 也是初学者最容易犯的错误,这个错误也比较容易发现。

例 2 可能会导致不能把 vectInt 中所有为 3 的元素删除掉。因为第一次删除成功时, itVect = vectInt.erase( itVect );itVect 为指向 3 之后的位置,之后再执行 ++itVect , itVect 就掉过了被删除元素 3 之后的元素 3 ,导致只删除了一个为 3 的元素,这个 bug 比较隐蔽,因为如果不是两个均为 3 的元素相临,就将很难捕捉到这个 bug ,程序有可能在一段时间运行良好,但如碰到容器中两值相同的元素相临,则程序就要出问题。

例 3 ,对于本例你可能要说程序没有任何问题,解决了上面的两个 bug ,程序也运行正常。但且慢,你把 “ vectInt[ 4 ] = 4; ” 这一行改为 “ vectInt[ 4 ] = 3; ”试试,一运行,程序当掉,访问非法内存!你疑惑不解:从程序看不出 bug ,而且我还把 vectInt.end() 放在外面计算以防止 for 多重计算,提高效率。哈哈,问题就出在最后一句话!算法大师 Donald Knuth 有一句名言:不成熟的优化是一切恶果的根源( Permature optimization is the root of all evil )。由于在 for 循环中要删除元素,则 vectInt.end() 是会变化的,所以不能在 for 循环外计算,而是每删除一次都要重新计算,所以应放在 for 循环内。那你要问,为什么把 “ vectInt[ 4 ] = 4; ” 这一行改为 “ vectInt[ 4 ] = 3; ”程序就会当掉,而不改程序就很正常呢?这就跟 vector 的实现机制有关了。下面以图例详细解释。

vectInt 的初始状态为:

 

                                                                             | end

0        1        2        3         4     

                                     

删除 3 后,

 

                                                                 | 新的 end  | 原来的 end

0         1        2        4         4     

 

 

 

注意上面“新的 end ”指向的内存并没有被清除,为了效率, vector 会申请超过需要的内存保存数据,删除数据时也不会把多余的内存删除。

然后 itVect 再执行 ++itVect ,因为此时 *itVect 等于 4 ,所以继续循环, 这时 itVect 等于“新的 end ”但不等于“原来的 end ”(它即为 itVectEnd ),所以继续,因为 *itVect 访问的是只读内存得到的值为 4 ,不等于 3 ,故不删除,然后执行 ++itVect 此时 itVect 等于 itVectEnd 退出循环。从上面过程可以看出,程序多循环了一次(删除几次,就要多循环几次),但程序正常运行。

如果把 “ vectInt [ 4 ] = 4; ” 这一行改为 “ vectInt [ 4 ] = 3; ”过程如下:

 

 

                                                                        | end

0        1        2        3         3     

 

删除 3 后,

 

                                                                 | 新的 end       | 原来的 end

0        1        2        3         3      

 

 

删除第 2 个 3 后,

 

                                                 | 新的 end            | 原来的 end

0        1        2        3         3     

 

这时 itVect 等于“新的 end ”但不等于“原来的 end ”(它即为 itVectEnd ),所以继续,因为 *itVect 访问的是只读内存得到的值为 3 ,等于 3 ,所以执行删除,但因为 *itVect 访问的是只读内存不能删除,所以程序当掉。

综上,我们知道当要删除的值在容器末尾时,会导致程序删除非法内存,程序当掉;即使程序正常运行,也是 for 循环多执行了等于删除个数的循环。所以把 vectInt.end() 放在 for 循环外面执行,完全是错误的。对于 list 容器, list.end() 在删除过程中是不会变的,可以把它放在 for 循环外面计算,但由于 list.end() 是个常量,把 list.end() 放在 for 循环中计算编译器应该可以优化它。从安全考虑,除非你能保证 for 循环中不会改变容器的大小,否则都应该对容器的值在 for 循环中计算,对于 vectInt.size() 这样的计算,也应该在 for 循环中计算,不要因为微小的优化而导致程序出错。

 

正确的方法:

例 4 :

#include <iostream>

#include <vector>

using namespace std;

void main( ) {

       vector<int> vectInt;

       int i;

       for (  i = 0; i < 5; i++ ) {

              vectInt.push_back( i );

              if ( 3 == i ) {

                     //       使 3 的元素有两个,并且相临。

                     vectInt.push_back( i );

              }

       }

       vector<int>::iterator itVect = vectInt.begin();

       //     以下代码是要删除所有值为 3 的元素

       for ( ; itVect != vectInt.end();  ) {  // 删除 ++itVect{

              if ( *itVect == 3 ) {

                     itVect = vectInt.erase( itVect );

              }

              else {

                     ++itVect;

              }

       }

       //     把 vectInt.size() 放在 for 循环中

      for (  i = 0 ; i < vectInt.size(); i++ )  {

                     cout << " i= " << i <<  ", " << vectInt[ i ] << endl;

       }

运行结果为:

i= 0, 0

i= 1, 1

i= 2, 2

i= 3, 4

从结果显示值为 3 的元素确实被删除了。

JAVA:Spring Boot3 集成 ShardingSphere 实现数据分库分表 本文介绍了基于Spring Boot和ShardingSphere-JDBC实现分库分表与读写分离的解决方案。针对高并发、大数据量场景下单库单表的性能瓶颈,通过配置ShardingSphere-JDBC实现:1)订单表按user_id和order_id分片到两个库和两张表;2)读写分离,写操作路由至主库,读操作在从库间负载均衡。方案使用Druid连接池优化,支持业务透明,无需修改SQL。 阅读详情

相关推荐

生信物种注释软件Kraken2+Bracken的安装与使用

一、软件安装与数据库下载 1.Kraken2安装 Kraken2是一个基于k-mer算法的高精度宏基因组序列分类软件,能够快速的将测序reads进行物种分类。 使用conda一键安装,要先安装好anaconda或者miniconda conda install -y kraken2 输入krakek2 --help 出现如下所示帮助文档,则表示安装成功。 2.Bracken安装 bracken是一种从宏基因组数据中高度准确的计算物种丰度的统计方法。 同样使用conda一键安装 conda install -

jileiqwd的博客 1万+

跟我学C++中级篇——STL中的删除

在STL中一般删除的方式有两类,一种是使用全局的std::remove(remove_if类似),一种是使用容器自带的erase,前者其实并没有真正的删除数据,而后者则是在移动时,会有一些细节的处理,否则要么程序崩溃,要么达不到删除的目的。下面就这两个函数进行一下分析说明。在一些容器中也提供了remove(如std::list)方法,它和全局的std::remove还是用法区别不小的,这个大家需要自己看一下。注:C++20提供了std::erase这种更方便的用法。

fpcc的专栏 898

U8二次开发CO-基于Net8调用COM对象

本文章介绍的U8二次开发CO技术,就是基于微软COM组件技术,使用微软开源的编程框架Net6/7/8/9来调用U8的COM组件,完成基本的登录认证,工作流处理,数据库操作等。

pccai的博客 5501

STL容器删除元素

这些容器都是连续内存的容器,删除一个会发生平移操作,由于容器中可能有多个指定值的元素,上述删除方法可以只移动一次,然后把指定值全删除掉。使用成员函数erase(),因为关联型容器底层都是平衡二叉树结构。因为list是链表形式的,直接remove就很高效。对于 vector、deque、string。

weixin_45611085的博客 437

张正友标定法_单目-线激光三维扫描系统中光刀平面的标定

单目-线激光三维扫描系统中光刀平面的标定线结构光有结构简单、精度高、测速快等特点,被广泛应用于三维重建领域。线结构光三维扫描系统由一个相机和一个线结构光投射器构成,线结构光投射器将线结构光投射到被测物体表面,相机采集带有线激光的被测物体图像,根据相机和线激光投射器间的相对位置关系,使用激光三角法计算出激光线上的点在相机坐标系下的坐标。激光三角法原理如下图所示,线激光投射的扇形区域可以看作是一个平面...

weixin_39616379的博客 1074

瑞芯微rv1126 rtsp+mpp+rga取流

瑞芯微rv1109 rtsp+mpp+rga取流

ZuoSeDiao的博客 6338

【STL】删除元素 remove、erase

【STL】删除元素 remove、erase STL功能很强大,但是说到删除元素,不少人会觉得有些上头。 删除元素,第一印象可能就是erase和remove。 remove remove,在STL不止一个,有全局std::remove函数,list容器中,还有一个名为remove的成员函数;两个函数的名称完全一样,很多人傻傻分不清。 有以下几点区别: std::remove适用于很多容器,list只是其中之一;而list的成员函数remove,既然是成员函数,肯定是它独享咯! std::remove

qq_43331089的博客 2475

使用lightGBM进行数据建模及预测

在机器学习领域中,数据建模和预测一直是非常重要的任务。而lightGBM作为一种基于梯度提升算法的高效机器学习框架,被广泛应用于数据建模和预测中。本文将介绍如何使用lightGBM进行数据建模和预测。到此,我们已经成功使用lightGBM对数据进行了建模和预测。使用lightGBM可以快速、高效地完成数据建模和预测任务,是机器学习领域中不可或缺的工具之一。使用lightGBM进行数据建模及预测。

qq_37934722的博客 1016

STL 使用问题 -- 慎重选择删除元素的方法

慎重选择删除元素的方法

xuwaiwai的博客 998

全球气候模式降尺度方法总结及其代码实现

简介总结各类型气候模式降尺度方法

WW、forever的博客 1万+

Win10 系统下VisualStudio2022 配置Open3D-0.18.0(C++)

Win10 系统下VisualStudio2022 配置Open3D-0.18.0

点云侠的博客 2655

STL序列式容器中删除元素的方法和陷阱 一

在STL(标准模板库)中经常会碰到要删除容器中部分元素的情况,本人在编程中就经常编写这方面的代码,在编码和测试过程中发现在STL中删除容器有很多陷阱,网上也有不少网友提到如何在STL中安全删除元素这些问题。本文将讨论编程过程中最经常使用的两个序列式容器vector、list中安全删除元素的方法和应该注意的问题,       其它如queue、stack等配接器容器(container adapte...

手机即信息 216

OPC UA协议深度解析 + C#跨平台实战:无缝对接西门子S7-1200/1500 PLC

本文完成了OPC UA协议深度解析→西门子PLC服务端配置→C#跨平台客户端开发→全平台部署拆解OPC UA核心模型与安全机制,从原理层面理解工业标准化通信逻辑基于官方SDK开发工业级客户端,支持读写、订阅、证书管理、心跳保活核心功能实现Windows、Linux、统信UOS全平台兼容,满足工控系统国产化改造刚需提供完整的优化方案与踩坑指南,可直接落地商用项目。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 518

转载: STL序列式容器中删除元素的方法和陷阱一 .

  在 STL (标准模板库)中经常会碰到要删除容器中部分元素的情况,本人在编程中就经常编写这方面的代码,在编码和测试过程中发现在 STL 中删除容器有很多陷阱,网上也有不少网友提到如何在 STL 中安全删除元素这些问题。本文将讨论编程过程中最经常使用的两个序列式容器 vector 、 list 中安全删除元素的方法和应该注意的问题,        其它如 queue 、 stack 等配接器...

weixin_34184158的博客 83

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

STL序列式容器中删除元素的方法和陷阱(一)

 在STL(标准模板库)中经常会碰到要删除容器中部分元素的情况,本人在编程中就经常编写这方面的代码,在编码和测试过程中发现在STL中删除容器有很多陷阱,网上也有不少网友提到如何在STL中安全删除元素这些问题。本文将讨论编程过程中最经常使用的两个序列式容器vector、list中安全删除元素的方法和应该注意的问题, 其它如queue、stack等配接器容器(container adapter),由于

Alvin-He的专栏 1084

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

STL序列式容器中删除元素的方法和陷阱 一 (转)

STL序列式容器中删除元素的方法和陷阱 一 (转)[@more@]在STL(标准模板库)中经常会碰到要删除容器中部分元素的情况,本人在编程中就经常编写这方面的代码,在编码和测试过程中发现在STL中删除容器有很多陷阱,网上也有不少...

csd3176的博客 154

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

基于GitLab+Jenkins+Docker+K8s的CICD平台搭建实战

持续集成与持续交付(CI/CD)是现代DevOps体系的核心实践,其目标是将代码提交、构建、测试、部署流程自动化,从而缩短交付周期并降低人为失误。这套体系以Docker容器技术作为环境一致性的基础,通过镜像仓库Harbor管理制品,并借助Kubernetes(K8s)实现容器的调度与滚动更新。对于中小团队而言,搭建一条覆盖代码托管、自动构建、镜像推送、集群部署的完整流水线,能够显著提升发布效率与稳定性。本文基于GitLab、Jenkins、Docker、Harbor、K8s与Rancher,提供一套经过验证

cuiji1279的博客 333

STL容器的删除元素问题

STL的容器分为两类,一类是序列式容器,即数据顺序连续存储,如:vector、deque;另一类是关联式容器,即数据不连续存储,如:map、list、set。对于STL容器的数据删除操作,有一些需要注意的地方。 1、序列式容器的数据删除        下面例子以容器list为例: [cpp] view plain copy for (list

weixin_36145588的博客 325

STL:循环删除容器中元素的方法和陷阱

算法大师Donald Knuth:不成熟的优化是一切恶果的根源(Permature optimization is the root of all evil )。 STL中的容器主要是两种:序列式容器和关联式容器。下面讲到的都是我在开发中曾经遇到过的一些问题,有些例子我做了修改,我想初学STL的人基本都会遇到这些问题。序列式容器:vector list string等等关联式容

magictong的专栏 5144

STL序列式容器中删除元素的方法和陷阱 二

STL序列式容器中删除元素的方法和陷阱 二 2.使用STL中通用算法或容器成员函数删除元素的方法以上手工编写for循环代码删除容器中元素的方法也有一些问题,如果判断条件特别复杂,又有循环判断的话,循环中间又有异常处理的话,++itVect的位置就要小心放置了,稍不留意就要出错。所以手工编写代码删除容器中元素的方法不太安全,代码重复,也不够优雅,要注意的地方很多。对于这种情况,可以考

hurtmanzc的专栏 1069

转载: STL序列式容器中删除元素的方法和陷阱二 .

2.使用 STL 中通用算法或容器成员函数删除元素的方法 以上手工编写 for 循环代码删除容器中元素的方法也有一些问题,如果判断条件特别复杂,又有循环判断的话,循环中间又有异常处理的话, ++itVect 的位置就要小心放置了,稍不留意就要出错。所以手工编写代码删除容器中元素的方法不太安全,代码重复,也不够优雅,要注意的地方很多。 对于这种情况,可以考虑使用 STL 中通用算法 remvoe...

weixin_34408624的博客 95

转载: STL序列式容器中删除元素的方法和陷阱三

3.list容器中删除元素的方法 对于list容器,由于list本身有remove和remove_if的成员函数,所以最好优先考虑list自己的算法,对于remove函数,比较简单,不再讨论,对于remove_if函数,本人发现在vc6.0中有重大问题。我试了多种函数对象,总是编译不过,通过查看源代码,才发现VC6.0中对remove_if()函数作了简化,只提供了一种比较函数,它只能删除不等于某...

weixin_34292924的博客 102

STL序列式容器中删除元素的方法和陷阱 三

STL序列式容器中删除元素的方法和陷阱 三 3.list容器中删除元素的方法对于list容器,由于list本身有remove和remove_if的成员函数,所以最好优先考虑list自己的算法,对于remove函数,比较简单,不再讨论,对于remove_if函数,本人发现在vc6.0中有重大问题。我试了多种函数对象,总是编译不过,通过查看源代码,才发现VC6.0中对remove_if()

hurtmanzc的专栏 1302
上一篇: 用template来判断类继承的关系
下一篇: STL序列式容器中删除元素的方法和陷阱二
CyberLogix
博客等级 码龄24年 397粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值