怎么从vector里删除元素

本文介绍了如何在C++ STL容器如vector、map和list中高效地删除元素,特别是展示了如何利用remove_if算法配合不同方式定义的判断条件来实现灵活且高效的元素移除。
     现在用stl的人越来越多, stl确实是套很漂亮的算法和数据结构库. 但是初用stl的人往往会遇上很多问题.
     从一个容器中删除元素,是很常用的操作,但是也是初学者常会犯错误的地方,删除map和list中元素可能会犯迭代器失效的错误. vector是stl里很常用的一个容器, 和map,list等容器相比, 从vector中删符合某些条件的元素有更多的麻烦.
比如,我们要完成如下的任务.
有下面的类
class  AA
{
public :
 AA():n( 0 ){}
 AA( int  b):n(b){}
  int  n;
};
 有个vector
vector<AA> vaa;
一个list
list<int> intList;
    现在需要执行这样的操作, 删除vaa里所有成员变量n在intList里的所有元素.那么, 应该怎么做呢?我们可以有下列选择:
1 手写循环
    仿照list的删除方法.
vector < AA > ::iterator ite  =  vaa.begin();
for  (; ite  !=  vaa.end(); )
{
     if  (find(intList.begin(), intList.end(),ite -> n)  !=  intList.end())
        vaa.erase( ++ ite);
     else
         ++ ite;
}
 
    一运行就会发现不行了, vector的erase的特点是, 被删除的元素和之后的所有元素的iterator都失效了, 即使保存了后面一个iterator, 也不能继续遍历了. 对于这种连续存储的序列, 应该是把不需要的元素用需要的代替, 然后把结尾不要的元素删除.像这样:
 
    vector < AA > ::iterator ite  =  vaa.begin();
    vector < AA > ::iterator dest  =  ite;
     for (; ite  !=  vaa.end();  ++ ite)
    {
         if  (find(intList.begin(), intList.end(),ite -> n)  ==  intList.end())
        {
             * dest ++   =   * ite;
        }
    }
    vaa.erase(dest, vaa.end()); 
 
2. 使用remove_if, 写一个判断函数作为条件.
    像上面那样写循环,麻烦,容易错而且不好读, STL提供了一个算法remove_if可以不用自己写循环,完成上面那个循环的功能, 就是把不要的
 
  元素用需要的元素代替, 返回结尾处的iterator.remove_if的原型为
 
template  < class  ForwardIterator,  class  Predicate >
ForwardIterator remove_if(ForwardIterator first, ForwardIterator last,Predicate pred); 
 
    pred是一个函数子,用来作为判断条件. 函数子是可以按照函数调用的语法来使用的类型, 它可以是一个函数指针, 也可以是一个重载了operator()的类型.这里pred要求是返回值是bool,有一个参数的函数子, 参数类型就是容器里元素的类型, 对每个元素执行这个函数, 返回true就会被remove.
    所以,我们需要先写一个函数来判断一个AA类型的变量是否满足条件. 但是, 这个函数显然需要两个参数, 一个AA 和一个list<int>,为了避免拷贝,我们用指针传递list
 
     bool  inTheList( AA aa,  const  list < int >   * lint)
    {
         return  find(lint -> begin(), lint -> end(), aa.n)  !=  lint -> end();
    }
 
    要把这个两个参数的函数绑定上一个参数变成一个参数的函数, 可以使用stl里的bind2nd函数,原型如下
 
template  < class  AdaptableBinaryFunction,  class  T >
binder2nd < AdaptableBinaryFunction > 
bind2nd( const  AdaptableBinaryFunction &  F,  const  T &  c); 
 
    这个函数并不会被执行, 编译器只是靠它来做类型推导, 它会返回一个Adaptable unary function 类型. 它的第一个参数是一个Adaptable Binary Function, 它是一个重定义了operator()的类型,不能直接传一个函数指针, 所以我们需要ptr_fun函数,ptr_fun对双参函数指针的重载的原型为:
 
template  < class  Arg1,  class  Arg2,  class  Result >
pointer_to_binary_function < Arg1, Arg2, Result > 
ptr_fun(Result ( * x)(Arg1, Arg2)); 
 
这个函数也是用来做类型推导的, 可以返回一个Adaptable unary function.
 
综合以上各个函数, 于是就可以这样写了:
 
vaa.erase(remove_if(vaa.begin(), vaa.end(),bind2nd(ptr_fun(inTheList), & intList)), vaa.end()); 
 
注意, 可能是vc6的bug, 如果inList是一个类的静态成员函数, 上面的写法在vc6里无法编译, vc6不能推导出函数子的类型,上面的写法在vc8和gcc中是可以的.对于vc6,需要显式的告诉编译器我们传的是函数指针,像下面这样
 
vaa.erase(remove_if(vaa.begin(), vaa.end(),bind2nd(ptr_fun( & inTheList), & intList)), vaa.end()); 
 
我们也可以让inTheList是AA的一个成员函数
 
bool  AA::inTheList( const  list < int >   * lint)
{
     return  find(lint -> begin(), lint -> end(), n)  !=  lint -> end();

 
stl提供了一套把成员函数转为单参或双参函数子的函数,mem_fun1_ref,这里我们用上面的删除操作就可以写成:
 
vaa.erase(remove_if(vaa.begin(), vaa.end(),bind2nd(mem_fun1_ref( & AA::inTheList), & intList)), vaa.end()); 
 
 
3, 还是用remove_if, 自己定义判断条件的函数子类型
上面那套转换和绑定肯定能让人抓狂, 使用函数指针来传递判断条件也比较低效. 我们可以自己定义一个类型
 
class  InListFunctor
{
public :
    InListFunctor( const  list < int >   & lint):m_list(lint)
    {}
     bool   operator  ()(AA a)
    {
         return  find(m_list.begin(), m_list.end(), a.n)  !=  m_list.end();
    }
private :
     const  list < int >   & m_list;
} ; 
 
 
这样就可以直接传给remove_if了, InListFunctor的构造函数接受一个list<int>的const引用, 可以把要比较的list传进去.
 
vaa.erase(remove_if(vaa.begin(), vaa.end(), InListFunctor(intList)), vaa.end()); 
 
通过自己定义的函数子,可以构造很复杂的比较条件,更加方便和自由.
 
4, 用boost::lambda, 构造匿名函数.
上面两个方法都有个共同的缺点, 要么要定义一个函数, 要么要定义一个类型, 这都会给一个类里添加不必要的东西,这在实际编程中会让人觉得不爽. C++0X 里面提供了 lambda ,可以构造匿名函数。这个特性已经在 vs2010 里面实现了。于是可以用下面的代码来实现删除。
    vaa.erase(remove_if(vaa.begin(), vaa.end(),
                [&intList](const AA &a){
                    return find(intList.begin(), intList.end(), a.n) != intList.end();
                }),
          vaa.end());

删除所有偶数项,并打印出删除的项
1. vector/queue

正确方法1:
void erase(vector<int> &v)
{
for(vector<int>::iterator vi=v.begin();vi!=v.end();)
{
if(*vi % 2 == 0)
{
cout
<< "Erasing " << *vi << endl;
vi
= v.erase(vi);
}
else ++vi;
}
}

正确方法2:
void erase2(vector<int> &v)
{
for(vector<int>::reverse_iterator ri=v.rbegin();ri!=v.rend();)
{
if(*ri % 2 == 0)
{
cout
<< "Erasing " << *ri << endl;
v.erase((
++ri).base()); //erase()函数期待的是正向iterator,故而这里要调
//用base()函数将逆向iterator转换为正向的
}
else ++ri;
}
}



2.map/list
正确方法

void erase(map<int,int> &m)
{
for(map<int,int>::iterator mi=m.begin();mi!=m.end();)
{
if(mi->second % 2 == 0)
{
cout
<< "Erasing " << mi->second << endl;
m.erase(mi
++);
}
else ++mi;
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值