




已阅读5页,还剩14页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
STL序列式容器中删除元素的方法和陷阱在STL(标准模板库)中经常会碰到要删除容器中部分元素的情况,本人在编程中就经常编写这方面的代码,在编码和测试过程中发现在STL中删除容器有很多陷阱,网上也有不少网友提到如何在STL中安全删除元素这些问题。本文将讨论编程过程中最经常使用的两个序列式容器vector、list中安全删除元素的方法和应该注意的问题, 其它如queue、stack等配接器容器(container adapter),由于它们有专属的操作行为,没有迭代器(iterator),不能采用本文介绍的删除方法,至于deque,它与vector的删除方法一样。STL容器功能强大,but no siliver bullet,如果你使用不当,也将让你吃尽苦头。1. for循环代码删除STL序列式容器中元素的方法例如,你能看出以下代码有什么问题?例1:#include #include using namespace std;void main( ) vector vectInt; int i; / 初始化vector容器 for (i = 0; i 5; i+ ) vectInt.push_back( i ); / 以下代码是要删除所有值为4的元素 vector: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 #include using namespace std;void main( ) vector vectInt; int i; / 初始化vector容器 for ( i = 0; i 5; i+ ) vectInt.push_back( i ); if ( 3 = i ) / 使3的元素有两个,并且相临。这非常关键,否则将发现不了bug。 / 具体解释见下。 vectInt.push_back( i ); vector:iterator itVect = vectInt.begin(); vector: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 #include using namespace std;void main( ) vector vectInt( 5 ); int i; vectInt 0 = 0; vectInt 1 = 1; vectInt 2 = 2; vectInt 3 = 3; vectInt 4 = 4; / 替换为 vectInt 4 = 3;试试 vector:iterator itVect = vectInt.begin(); vector: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的初始状态为: | end0 1 2 3 4 删除3后, |新的end | 原来的end0 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;”过程如下: | end0 1 2 3 3 删除3后, |新的end |原来的 end0 1 2 3 3 删除第2个3后, |新的end |原来的 end0 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 #include using namespace std;void main( ) vector vectInt; int i; for ( i = 0; i 5; i+ ) vectInt.push_back( i ); if ( 3 = i ) / 使3的元素有两个,并且相临。 vectInt.push_back( i ); vector: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, 0i= 1, 1i= 2, 2i= 3, 4从结果显示值为3的元素确实被删除了。2.使用STL中通用算法或容器成员函数删除元素的方法以上手工编写for循环代码删除容器中元素的方法也有一些问题,如果判断条件特别复杂,又有循环判断的话,循环中间又有异常处理的话,+itVect的位置就要小心放置了,稍不留意就要出错。所以手工编写代码删除容器中元素的方法不太安全,代码重复,也不够优雅,要注意的地方很多。对于这种情况,可以考虑使用STL中通用算法remvoe()和remove_if()帮忙。而remvoe()和remove_if()这两个算法也有一个问题需要程序员特别小心。在通用算法中的 remove(包括remove_if) 函数,并不真正从容器中删除元素,而是“应被删除的元素”被其后的“未被删除的元素”覆盖。返回值ForwardIterator指向经移除后的最后元素的下一位置。如vector0,1,2,3,3,4,执行remove(),希望移除所有值为3的元素,结果为0,1,2,4,3,4,返回值ForwardIterator指向第5个元素。即:0 1 2 3 3 4 移除前0 1 2 4 3 4 移除后移除值为3的元素。移除后3被其后的4替代,最后两位元素为残余数据。例 5:void main() vector vectInt; int i; for (i = 0; i 5; i+ ) vectInt.push_back( i ); if ( 3 = i ) vectInt.push_back( i ); remove( vectInt.begin(), vectInt.end(), 3 ); cout after deleted , size = vectInt.size() endl; for ( i = 0; i vectInt.size(); i+ ) cout i = i , vectInti endl; 运行结果为:after deleted , size = 6 / 从这行可以看出,移除后容器的大小没变i = 0 , 0i = 1 , 1i = 2 , 2i = 3 , 4 / 从这行可以看出:“应被删除的元素”3 被其后的“未被删除的元素”4覆盖i = 4 , 3i = 5 , 4 所以要彻底删除还应该把后面的残余数据删除掉,这可以通过调用容器的成员函数erase()做到。例 6:void main() vector vectInt; int i; for (i = 0; i 5; i+ ) vectInt.push_back( i ); if ( 3 = i ) vectInt.push_back( i ); vectInt.erase( remove( vectInt.begin(), vectInt.end(), 3 ), vectInt.end() ); cout after deleted , size = vectInt.size() endl; for ( i = 0; i vectInt.size(); i+ ) cout i = i , vectInti endl; 运行结果为:after deleted , size = 4 / 从这行可以看出,删除后容器的大小变化了i = 0 , 0i = 1 , 1i = 2 , 2i = 3 , 4从结果可以看出,所有值为3的元素确实被删除了。对于vector容器存放其他比较复杂的对象,就可以用remove_if()加函数对象(Function Object)的方法。如:例7:#include #include #include #include #include #include using namespace std;class CTest public: CTest( const string& str, int iPrice ) : m_strName( str ), m_iPrice( iPrice ) void vPrint() cout name= m_strName price = m_iPrice endl; private: string m_strName; int m_iPrice; / 由于两个函数对象要访问CTest类的private成员,所以设为友员。 friend class CStrFunc; friend class CIntFunc;/ 函数对象,根据string比较class CStrFunc string m_str;public: CStrFunc( const string& str ) : m_str( str ) bool operator() ( const CTest& left ) return ( m_str = left.m_strName ) ? true : false; ;/ 函数对象,根据int比较class CIntFunc int m_iPrice;public: CIntFunc( int iPrice ) : m_iPrice( iPrice ) bool operator() ( const CTest& left ) return ( m_iPrice = left.m_iPrice ) ? true : false; ;void main( ) vector vectTest; int i; for ( i = 0; i 5 ; i+ ) stringstream stream; / 流格式化符,把int转化为string stream i; string str = stream.str(); CTest clTest( str, i ); vectTest.push_back( clTest ); for ( i = 0 ; i vectTest.size(); i+ ) vectTest i .vPrint(); / 删除所有m_strName = 3的元素 vectTest.erase( remove_if( vectTest.begin(), vectTest.end(), CStrFunc( 3 ) ), vectTest.end() ); cout delete 3 after : endl; for ( i = 0 ; i vectTest.size(); i+ ) vectTest i .vPrint(); / 删除所有m_iPrice = 2的元素 vectTest.erase( remove_if( vectTest.begin(), vectTest.end(), CIntFunc( 2 ) ), vectTest.end() ); cout delete 2 after : endl; for ( i = 0 ; i vectTest.size(); i+ ) vectTest i .vPrint(); 手工编写for循环代码删除STL序列式容器中元素的方法,使用STL中通用算法或容器成员函数删除元素的方法,两者之间的比较:1 前者代码重复。2 前者容易出错,不够清晰。3 效率:0 1 2 3 2 5 6 70 1 3 2 5 6 70 1 3 5 6 7 用第一种方法删除所有值为2的元素从上图可以看出,每删除一个元素,后面的所有元素都到往前移动一位,导致一次内存大搬迁。0 1 2 3 2 5 6 70 1 3 2 5 6 6 70 1 3 5 6 7 用第二种方法删除所有值为2的元素从上面可以看出,删除时元素2被后面元素覆盖,不会到元素移位和内存大搬迁,残余数据留到末尾一次全部删除,也不会导致内存大搬迁,所以后者的方法要比前者在效率上好很多。3.list容器中删除元素的方法对于list容器,由于list本身有remove和remove_if的成员函数,所以最好优先考虑list自己的算法,对于remove函数,比较简单,不再讨论,对于remove_if函数,本人发现在vc6.0中有重大问题。我试了多种函数对象,总是编译不过,通过查看源代码,才发现VC6.0中对remove_if()函数作了简化,只提供了一种比较函数,它只能删除不等于某值的元素,VC6.0种remove_if()函数的源码如下:typedef binder2ndnot_equal_to _Pr1; void remove_if(_Pr1 _Pr) iterator _L = end(); for (iterator _F = begin(); _F != _L; ) if (_Pr(*_F) erase(_F+); else +_F; 从源码中可以看出,remove_if中_Pr1函数对象被固定为binder2ndnot_equal_to 一种格式。而在VC7.0中已经修改了这个bug,源码如下:template void remove_if(_Pr1 _Pred) / erase each element satisfying _Pr1 iterator _Last = end(); for (iterator _First = begin(); _First != _Last; ) if (_Pred(*_First) erase(_First+); else +_First; 在VC7.0中remove_if()是成员模板函数,可以用任何判断条件的函数对象。例如:例 8:#include #include #include #include using namespace std;class CTestpublic: CTest( int i ) : m_iPrice ( i ) int operator = ( const CTest& right ) const return ( m_iPrice = right.m_iPrice ) ? 1 : 0; int operator != ( const CTest& right ) const return ( m_iPrice != right.m_iPrice ) ? 1 : 0; int operator ( const CTest& right ) const return ( m_iPrice right.m_iPrice ) ? 1 : 0; private: int m_iPrice; friend class CTestFunc;class CTestFunc / 函数对象public: int m_value; CTestFunc( int i ) : m_value( i ) bool operator () ( const CTest& clFirst ) return ( clFirst.m_iPrice = m_value ) ? true : false; ;void main() list listTest; for ( int i = 0; i 5; i+ ) CTest clTest( i ); listTest.push_back( clTest ); cout remove before : listTest.size() endl;/ 删除所有为2的元素 listTest.remove_if( CTestFunc( 2 ) ); / 这条语句在vc6.0中不能编译通过,VC7.0中可以 cout remove after : 2, size = listTest.size() endl; / 删除所以不等于2的元素,VC6.0中只能以这种方式调用remove_if()函数 listTest.remove_if( bind2nd( not_equal_to(), 2 ) ); cout remove after not equal to 2, size = listTest.size() endl; / 因为CTest类提供了=、 成员函数,所以也可以用remove函数 listTest.remove( 2 ); / 删除所有为2的元素 cout remove after : 2, size = listTest.size() endl;不知道在VC6.0中能否突破只能函数对象被固定为binder2ndnot_equal_to 一种格式的限制?欢迎诸位大虾不吝赐教。不过采用通用算法remove_if只是多了几次对象的赋值的负担,如果对象不是太大,用通用算法的性能也是可以接受的。另外,这些天使用了VC7.0后,感觉非常棒,不仅几乎符合Standard C+规范,错误提示也更清晰,而编译速度和编译后的文件大小大大减小,如我原来的一个大量使用了模板的程序,用VC6.0编译后Release版的可执行文件大小为1.2M,用VC7.0编译后只有420K,我想可能VC7.0在代码优化和模板代码的膨胀等方面有了极大的改善;在STL的实现上也有了极大的改进,把原来的一些效率不好的地方都改进了,处理策略基本与SGI STL一致。4.STL容器中元素为指针情况下的删除方法对于容器中的元素为指针的删除方法。如果容器中的元素为指针则不能用上面介绍的用通过算法或成员函数的方法删除元素,因为那样做会导致内存泄露,容器中的元素为指针指向的内存没有释放,在这种情况下有以下方法解决:1 尽可能不用指针作为容器的元素。2 如果是因为要减少对象拷贝和赋值方面的负担,而要在容器中存放指针的话,可以考虑用boost库中的智能指针shared_ptr包装指针,达到容器中引用的语意。3 如果你不希望因为使用boost:shared_ptr增加引用计数的负担,认为引入智能指针不好理解,那么你用指针作为容器的元素要千万小心,这时你要自己管理内存。例如: 例 9:用boost库中的智能指针shared_ptr包装指针的例子:#include #include #include #include #include #include #include / 要包含BOOST类库中智能指针的头文件using namespace std;class CTest public: CTest( const string& str, int iPrice ) : m_strName( str ), m_iPrice( iPrice ) void vPrint() cout name= m_strName price = m_iPrice endl; private: string m_strName; int m_iPrice; friend class CStrFunc; friend class CIntFunc;/ 函数对象,根据string比较class CStrFunc string m_str;public: CStrFunc( const string& str ) : m_str( str ) / 此处要改为boost:shared_ptr&,因为vector容器中的元素为/ boost:shared_ptr bool operator() ( const boost:shared_ptr& left ) return ( m_str = (*left).m_strName ) ? true : false; ;/ 函数对象,根据int比较class CIntFunc int m_iPrice;public: CIntFunc( int iPrice ) : m_iPrice( iPrice ) / 此处要改为boost:shared_ptr&,因为vector容器中的元素为/ boost:shared_ptr bool operator() ( const boost:shared_ptr& left ) return ( m_iPrice = (*left).m_iPrice ) ? true : false; ;void main( ) vector boost:shared_ptr vectTest; int i; for ( i = 0; i 5 ; i+ ) stringstream stream; stream i; string str = stream.str(); boost:shared_ptr ptrShare( new CTest( str, i ) ); vectTest.push_back( ptrShare ); for ( i = 0 ; i vectTest.size(); i+ ) ( *vectTest i ).vPrint(); / 删除所有m_strName = 3的元素 vectTest.erase( remove_if( vectTest.begin(), vectTest.end(), CStrFunc( 3 ) ), vectTest.end() ); cout delete 3 after : endl; for ( i = 0 ; i vectTest.size(); i+ ) ( *vectTest i ).vPrint(); / 删除所有m_iPrice = 2的元素 vectTest.erase( remove_if( vectTest.begin(), vectTest.end(), CIntFunc( 2 ) ), vectTest.end() ); cout delete 2 after : endl; for ( i = 0 ; i vectTest.size(); i+ ) ( *vectTest i ).vPrint(); 以上代码不会导致内存泄露。例 10:自己编程删除容器中元素为指针的例子:#include #include #include #include #include using namespace std;class CTest public: CTest( const string& str, int iPrice ) : m_strName( str ), m_iPrice( iPrice ) void vPrint() cout name= m_strName price = m_iPrice endl; private: string m_strName; int m_iPrice; / 声明友员函数,因为vDeleteVector函数要访问CTest的private成员变量 friend void vDeleteVector( vector& vectTest, const string& str ); friend void vDeleteVector( vector& vectTest, int iPrice );/ 根据CTest类中m_strName比较void vDeleteVector( vector& vectTest, const string& str ) vector:iterator itVect = vectTest.begin(); for ( ; itVect != vectTest.end(); ) if ( (*itVect)-m_strName = str )
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年智慧城市行业数字孪生城市模型研究报告
- 2025年新媒体行业媒体营销与内容创作研究报告
- 2025年绿色能源行业市场潜力及投资机会分析报告
- 2025年软件开发行业软件应用开发与软件工程研究报告
- 2025年可再生能源行业太阳能电池技术创新研究报告
- 2025年新零售行业线上线下融合模式研究报告
- 2025年文化传媒行业全民阅读推动战略研究报告
- 2025年医药健康产业行业传统中医药现代化发展报告
- 2025年神经内科疑难病例分析模拟考试答案及解析
- 2025重庆云阳县消防救援局政府专职消防员招聘16人笔试模拟试题及答案解析
- 拍照摄影技巧
- 校园招聘服务协议书范本
- 语音厅运营基础知识培训
- AIGC艺术设计 课件全套 第1-8章 艺术设计的新语境:AI的介入 -AIGC艺术设计的思考与展望
- 广州市房屋租赁合同国土局标准模版
- 停车场保安安全知识培训课件
- 校长在食堂从业人员培训会上的讲话
- (高清版)DBJ∕T 13-91-2025 《福建省房屋市政工程安全风险分级管控与隐患排查治理标准》
- 雅思小作文教学课件
- 电气柜安装服务合同范本
- 2025至2030中国硅单晶生长炉行业项目调研及市场前景预测评估报告
评论
0/150
提交评论