一、错误示范
在应用中,我们通常不可避免地要对容器中的某些特定元素进行删除操作。这看起来并不是什么困难的问题。我们先写一个循环来迭代容器中的元素,如果迭代元素是要删除的元素,则删除之。代码如下所示:
vector<int> intContainer;
for(vector<int>::iterator is = intContainer.begin(); it != intContainer.end(); ++it)
{
if ( *it == 25)
intContainer.erase(it);
}
写出此代码的原意是将vector中值为25的元素删除,但不幸的是,这样做是错误的,这么做会带来诡异的未定义行为。因为当一个容器的一个元素被删除时,指向那个元素的所有迭代器将失效。当intContainer.erase(it)返回时,it已经失效。在for循环中对于失效的it执行自增操作,这是一件多么不靠谱的事情啊。
二、vector 删除
既然这样行不通,那么我们可以求助于STL提供的remove算法。借助remove算法来达到删除元素的目的。
vector<int> intContainer;
size_t before_size = intContainer.size();
remove(intContainer.begin(), intContainer.end(), 25);
size_t after_size = intContainer.size();
运行程序以后发现 before_size 和 after_size 是一样的,说明元素并没有被真正删除。写出以上程序,是处于对remove算法的不了解而致。STL中remove算法会将不该删除的元素前移,然后返回一个迭代器,该迭代器指向的是那个应该删除的元素,仅此而已。所以如果要真正删除这一元素,在调用remove之后还必须调用erase,这就是STL容器元素删除的”erase_remove”的惯用法。
vector<int> intContainer;
intContainer.erase( remove(intContainer.begin(), intContainer.end(), 25), intContainer.end());
三、list 删除
erase-remove的惯用法适用于连续内存容器,比如vector,deque和string,它也同样适用于list,但是并不是我们推荐的方法,因为使用list成员函数remove会更高效,代码如下:
list<int> list_int;
....
list_int.remove(25);
四、map 删除
如果是关联容器呢?标准关联容器没有remove成员函数,使用STL算法的remove函数时编译同不过。所以上述remove形式对于标准关联容器并不适用。在这种情况下,解决办法就是调用erase:
map<int, int> mapContainer;
...
mapContainer.erase(25);
对于标准关联容器,这样的元素删除方式是简单有效的,时间复杂度为O(logn)
当我们需要删除的不是某一个元素,而是具备某一条件的元素的时候,我们只需要将remove替换成remove_if即可
bool Is2BeRemove(int value)
{
return value < 25;
}
vector<int> nVec;
list<int> nList;
....
nVec.erase(remove_if(nVec.begin(), nVec.end(), Is2BeRemove), nVec.end());
nList.remove_if(Is2BeRemove);
(1)对于节点式容器(map, list, set)元素的删除,插入操作会导致指向该元素的迭代器失效,其他元素迭代器不受影响;
(2)对于顺序式容器(vector,string,deque)元素的删除、插入操作会导致指向该元素以及后面的元素的迭代器失效。
http://ivan4126.blog.163.com/blog/static/209491092201351441333357/
正确删除法一:
(1)当删除特定值的元素时,删除元素前保存当前被删除元素的下一个元素的迭代器。
map<string,int >::iterator nextIt=countMap.begin();
for(map<string,int>::iterator it=countMap.begin(); ; ){
if(nextIt!=countMap.end())
++nextIt;
else break;
if(it->second==2){
countMap.erase(it);
}
it=nextIt;
}
(2)如何更加简洁的实现该方法呢?下面给出该方法的《Effective STL》一书的具体实现:
for(auto iter1 = theMap.begin(); iter1 != theMap.end(); )
{
if(iter1->second == xxx)
{
theMap.erase(iter1++); //#1
}else
{
++iter1;
}
}
(3)也可以实现如下,利用函数返回值(返回下一个iterator)
for(map<string,int>::iterator it=countMap.begin();it!=countMap.end();){
if(it->second==2){
it=countMap.erase(it);
}else
++it;
}
正确删除法二:
使用 remove_copy_if 实现
当删除满足某些条件的元素,可以使用remove_copy_if & swap方法。当然,方法一的满足特性值是满足某些条件的特例,因此,也可以应用此方法。
那么如何通过remove_copy_if 删除 map< string,int>中的元素呢?网上很少有给出map的实现,一般都是以vector为例。所以这里给出我的实现。
在通过remove_copy_if 按照条件拷贝了需要的元素之后,如何实现两个map的交换,可以直接调用map的成员函数swap。参考代码:
#include <iostream>
#include <string>
#include <map>
#include <algorithm>
#include <iterator>
using namespace std;
map<string,int> mapCount;
//不拷贝的条件
bool notCopy(pair<string,int> key_value){
return key_value.second==1;
}
int main(){
mapCount.insert(make_pair("000",0));
mapCount.insert(make_pair("001",1));
mapCount.insert(make_pair("002",2));
mapCount.insert(make_pair("003",1));
map<string,int> mapCountTemp;//临时map容器
//之所以要用inserter()函数是因为通过调用insert()成员函数来插入元素,并由用户指定插入位置
remove_copy_if(mapCount.begin(),mapCount.end(),inserter(mapCountTemp,mapCountTemp.begin()),notCopy);
mapCount.swap(mapCountTemp);//实现两个容器的交换
cout<<mapCount.size()<<endl; //输出2
cout<<mapCountTemp.size()<<endl; //输出4
//验证
//正确输出:
//000 0
//002 2
for(map<string,int>::iterator it=mapCount.begin();it!=mapCount.end();++it){
cout<<it->first<<" "<<it->second<<endl;
}
getchar();
}
这种方法的缺点:虽然实现两个map的交换的时间复杂度是常量级,一般情况下,拷贝带来的时间开销会大于删除指定元素的时间开销,并且临时map容器也增加了空间的开销。
总结:
关于容器的删除,有篇blog总结的很好,现在转贴如下:
删除容器中具有特定值的元素:
(1)如果容器是vector、string或者deque,使用erase-remove的惯用法。如果容器是list,使用list::remove。如果容器是标准关联容器,使用它的erase成员函数。
删除容器中满足某些条件的元素:
(2)如果容器是vector、string或者deque,使用erase-remove_if的惯用法。如果容器是list,使用list::remove_if。如果容器是标准关联容器,使用remove_copy_if & swap 组合算法,或者自己协议个遍历删除算法。
参考资料:李健《编写高质量C++代码》第七章,用好STL这个大轮子。
参考资料
[1] http://blog.youkuaiyun.com/lalor/article/details/7642797