STL容器删除的正确打开方式

一、错误示范

在应用中,我们通常不可避免地要对容器中的某些特定元素进行删除操作。这看起来并不是什么困难的问题。我们先写一个循环来迭代容器中的元素,如果迭代元素是要删除的元素,则删除之。代码如下所示:

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.csdn.net/lalor/article/details/7642797

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值