c++ vector刪除元素

licup123發表於2009-01-05
stl確是套很漂亮的演算法和資料結構庫. 但是初用stl的人往往會遇上很多問題.
從一個容器中刪除元素,是很常用的操作,但是也是初學者常會犯錯誤的地方,刪除map和list中元素可能會犯迭代器失效的錯誤. vector是stl裡很常用的一個容器, 和map,list等容器相比, 從vector中刪符合某些條件的元素有更多的麻煩.
比如,我們要完成如下的任務.
有下面的類
class AA
{
public
:
 AA():n(
0
){}
 AA(
int
 b):n(b){}
 
int
 n;
};

 有個vector
vector vaa;
一個list
list 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,為了避免拷貝,我們用指標傳遞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的const引用, 可以把要比較的list傳進去.

 

vaa.erase(remove_if(vaa.begin(), vaa.end(), InListFunctor(intList)), vaa.end());

 

通過自己定義的函式子,可以構造很複雜的比較條件,更加方便和自由.

 

4, 用boost::lambda, 構造匿名函式.

上面兩個方法都有個共 同的缺點, 要麼要定義一個函式, 要麼要定義一個型別, 這都會給一個類裡新增不必要的東西,這在實際程式設計中會讓人覺得不爽. 用boost::lambda可以構造匿名函式子, 不會給類的名字空間帶來汙染. 不過這個工作對boost::lambda來說有點複雜,需要包含下面三個boost::lambda標頭檔案,開啟boost::lambda的名字空 間.

 

#include <boost/lambda/lambda.hpp>
#include 
<boost/lambda/bind.hpp>
#include 
<boost/lambda/algorithm.hpp>
using namespace boost::lambda;

 

這個刪除操作可以寫成:

 

    vaa.erase(remove_if(vaa.begin(), 
        vaa.end(), 
        bind(ll::find(), 
            intList.begin(), 
            intList.end(),(
&_1)->*&AA::n)!=
intList.end()),
        vaa.end());

 

看起來有點複雜,關於boost::lambda的具體用法, 可以參考它的文件. 我一句兩句也說不清. 上式中_1是lambda的關鍵, 指的是生成的函式的第一個引數. 這裡也就是AA型別的元素.

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10697500/viewspace-526735/,如需轉載,請註明出處,否則將追究法律責任。

相關文章