1. 程式人生 > >STL容器的刪除元素問題

STL容器的刪除元素問題

       STL的容器分為兩類,一類是序列式容器,即資料順序連續儲存,如:vector、deque;另一類是關聯式容器,即資料不連續儲存,如:map、list、set。對於STL容器的資料刪除操作,有一些需要注意的地方。

1、序列式容器的資料刪除

       下面例子以容器list為例:

for (list<int>::iterator iter = lst.begin(); iter != lst.end(); iter++)
{
    if (符合條件)
    {
         lst.erase(iter);
    }
}

       上面的程式碼,乍一看好像是沒有什麼問題,一個再普通不過的for迴圈而已,怎麼會有問題呢?問題就是出在erase函式上了,對於關聯式容器,erase函式會刪除傳入的迭代器指向的元素,並回收迭代器指向的記憶體空間,此時該迭代器已經失效。這下問題就清晰了,上面的程式碼若進行了erase操作後,迭代器iter已經失效了,而for迴圈的iter++接著會對一個已失效的迭代器iter進行++操作,這種操作不可預期,可能會造成程式crash。


       正確的使用方法有以下兩種:
方法一:
       當erase函式執行完之後,會返回下一個有效節點,因此可以使用以下方法

for (list<int>::iterator iter = lst.begin(); iter != lst.end();)
{
    if (符合條件)
    {
        iter = lst.erase(iter);
    }
    else
    {
        iter++;
    }
}

方法二:
       下面寫法中,iter的後置++操作會完成兩件事,第一是返回iter的一個副本,這個副本和iter本身都指向同一記憶體地址;第二是對iter自身進行了++操作,此時iter還是有效的,而且指向下一個有效節點。而副本則作為實參傳給了erase函式,在erase函式中,副本所指的記憶體空間會被回收,副本會失效。可以簡單理解為,在iter被刪除之前就已經執行了++操作。

for (list<int>::iterator iter = lst.begin; iter != lst.end();)
{
    if (符合條件)
    {
        lst.erase(iter++);
    }
    else
    {
        iter++;
    }
}

2、序列式容器的資料刪除

       對於序列式容器vector、deque的資料刪除操作,也有一些點需要注意的,以vector為例,下面程式碼目標是刪除vector中滿足條件的資料:

vector<int> vec;

//插入資料
for (int i = 0; i < 5; ++i)
{
    vec.push_back(i);
}

//過濾並列印資料
for (vector<int>::iterator iter = vec.begin(); iter != vec.end(); ++iter)
{	
    //刪除值為0的資料
    if (0 == *iter)
    {
        vec.erase(iter);
    }
    else
    {
        cout<<*iter<<"\t";
    }
}
       我們期望的輸出結果應該是:1 2 3 4

       可是,上面程式碼實際輸出結果卻是:2 3 4,為什麼會出現這種情況呢?這就涉及到容器vector本身資料結構的特點和erase()函式的作用問題了。由於vector是序列式容器,資料在其中是連續儲存的,當呼叫了erase()函式刪除其中的某個元素時,會將刪除元素的下一個有效元素覆蓋刪除元素的所在位置的資料。
       這下子,對於上面程式碼的輸出結果就清晰了,當0元素被刪除後,iter已經指向了1元素,而for迴圈的++iter操作則將iter再遞增一次,使得iter指向了2元素,導致跳過了1元素,因此就出現了上面的輸出結果了。假如有兩個0元素連續存放,還會跳過第二個0元素的,導致沒有完全過濾掉滿足條件的資料,這個問題算是比較難發現的。

       上面的程式碼可能會導致滿足條件的資料沒有被刪除,但是在某些情況下,可能還會出現coredump問題,還是針對上面的例子,將上面程式碼的“過濾並列印資料”部分替換為以下程式碼,即刪除值為4的資料:

//過濾並列印資料
for (vector<int>::iterator iter = vec.begin(); iter != vec.end(); ++iter)
{
    //刪除值為4的資料
    if (4 == *iter)
    {
        vec.erase(iter);
    }
    else
    {
        cout<<*iter<<"\t";     
    }
}

       二話不說,程式直接segmentation fault。當執行完erase之後,iter已經指向vec.end(),而for迴圈的++iter,再將iter遞增一次,此時iter越界了,當執行*iter時,就導致了非法訪問記憶體,程式crash。