c++之你真的了解vector的erase嗎


以下針對vector容器,編譯環境為linux qt 4.7

篇幅較長,耐心看完,有錯誤歡迎指出

erase的定義

刪除容器內元素

erase的使用

先來看一下常用的寫法
第一種

   #include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<int> test{1,2,3};

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end(); test_iterator++)
    {
        if(*test_iterator == 2) {
            test.erase(test_iterator);
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}
    輸出:
    1
    3

網上對這種就是test.erase(test_iterator)之后test_iterator指向一個被刪除的地址,野指針不安全的,我現在告訴你不是的,代碼運行正常刪除了2,為什么正確往下看
第二種寫法

#include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<int> test{1,2,3};

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 2) {
            test.erase(test_iterator++);
        }
        else {
            test_iterator++;         
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}
結果:
  1
  3

也是對的,這種寫法是網上所謂的正確寫法之一,我告訴你這種是錯的,把數據元素換一下,換成1, 2 , 2 , 3
結果是
1
2
3
和代碼邏輯不符合,沒有達到預期,為啥呢,往下看
第三種寫法

#include <iostream>
#include <vector>
using namespace std;

int main() {

    vector<int> test{1, 2 , 2 , 3};

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 2) {
            test_iterator = test.erase(test_iterator);
        }
        else {
            test_iterator++;
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}

這是正確的寫法,根據erase的返回值描述,指向下一個元素(或end())的迭代器,當數據元素重復也能達到代碼邏輯正確,如果你僅僅為了刪除一個元素,那么看到這里就可以了,使用第三種寫法,保證你程序的正確

重點來了,如果你想了解為什么前兩種寫法會發生錯誤,下面給你講解

erase究竟做了什么?
看源代碼

    iterator
#if __cplusplus >= 201103L
    erase(const_iterator __position)
    { return _M_erase(begin() + (__position - cbegin())); }
#else
    erase(iterator __position)
    { return _M_erase(__position); }
#endif

   template<typename _Tp, typename _Alloc>
  typename vector<_Tp, _Alloc>::iterator
  vector<_Tp, _Alloc>::
  _M_erase(iterator __position)
  {
    if (__position + 1 != end())
  _GLIBCXX_MOVE3(__position + 1, end(), __position);
    --this->_M_impl._M_finish;
    _Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
    return __position;
  }

以上就是一個erase的完整實現,首先傳入一個迭代器,然后把傳入迭代器和cbegin()進行減法,然后加上begin(),得到的還是傳入的那個迭代器

然后進入_M_erase,先判斷以下,

1.如果位置不是最后一個元素,進行_GLIBCXX_MOVE3操作,_GLIBCXX_MOVE3其實就是std::copy,這里不講這個函數
只說作用,進行數據拷貝,把__position + 1, end()區間的元素拷貝到從__position開始的迭代器
2.--this->_M_impl._M_finish;,這個就是end()向前移動
尾指針進行向前移動,
3._Alloc_traits::destroy(this->_M_impl, this->_M_impl._M_finish);
進行元素的析構,注意這里進行的是對尾部元素的析構
4.返回傳入的迭代器,對傳入的迭代器沒有進行修改!!!
來一張圖片看一下整個過程

以上就是一個erase的核心過程

下面我們開始代碼驗證

#include <iostream>
#include <vector>
using namespace std;

class Test {
public:
    Test() {
        std::cout << "Test()" << std::endl;
    }
    Test(int i) {
        a = i;
        std::cout << "Test(int i)" << std::endl;
    }
    Test(const Test& e) {
        a = e.a;
        std::cout << "Test(const Test& e)" << std::endl;
    }
    Test& operator=(const Test& e) {
        std::cout << "Test& operator=(const Test& e) now = " << a << std::endl;
        std::cout << "Test& operator=(const Test& e) e = " << e.getA() << std::endl;
        a = e.a;
        return *this;
    }

    int getA() const{
        return a;
    }

    ~Test() {
        std::cout << "~Test() = " << a << std::endl;
    }
private:
    int a;
};


int main() {

    vector<Test> test;

    Test test1(1);
    Test test2(2);
    Test test3(3);

    test.push_back(test1);
    test.push_back(test2);
    test.push_back(test3);

    for(vector<Test>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(test_iterator->getA() == 2) {
            std::cout << "erase start" << std::endl;
            test_iterator = test.erase(test_iterator);
            std::cout << "erase end" << std::endl;
        }
        else {
            test_iterator++;
        }
    }

    for(vector<Test>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << test_iterator->getA() << std::endl;
    }
    return 0;
}


輸出結果只看erase start開始到erase end的
erase start
Test& operator=(const Test& e) e = 3
~Test() = 3
erase end
1
3

定義一個簡單的Test類,使用成員變量a確定析構的是哪個類,進行test2(2)的erase,結果和分析一致,對元素值為2的元素調用賦值操作符,參數元素值為3的類進行拷貝,刪除后元素剩下的為 1 3!!

關於erase的返回值

經過上面的源碼分析,相比你也知道了,傳入的參數和返回值是同一個,不信嗎,代碼驗證,換一個簡單的

#include <iostream>
#include <vector>
using namespace std;



int main() {

    vector<int> test{1,2,3,4};



    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();)
    {
        if(*test_iterator == 3) {
            std::cout << &test_iterator << std::endl;  //test_iterator的地址
            std::cout << &*test_iterator << std::endl; //test_iterator指向空間的地址
            test_iterator = test.erase(test_iterator);
            std::cout << &test_iterator << std::endl;  //test_iterator的地址
            std::cout << &*test_iterator << std::endl; //test_iterator指向空間的地址
            std::cout << "erase end" << std::endl;
        }
        else {
            test_iterator++;
        }
    }

    for(vector<int>::iterator test_iterator = test.begin(); test_iterator != test.end();
        test_iterator++) {
        std::cout << *test_iterator << std::endl;
    }
    return 0;
}
結果
0x7ffef8e11d50
0x18b5c28
////////////////
0x7ffef8e11d50
0x18b5c28
erase end
1
2
4

test_iterator的地址本身就是不變的,它只不過是一個類似智能指針管理傳入的元素,通過iterator調用
&*test_iterator代表指向的空間也就是3的地址,可以看到刪除前后地址不變
改一下代碼
test.erase(test_iterator);
不進行賦值了打印地址,得到的仍然相同
標准庫描述的意思是返回值是指向刪除元素下一個元素的迭代器,這沒問題,因為刪除元素的地址的內容換成了下一個元素,然后把這個迭代器返回就是原來傳入的參數迭代器

所以最開始的那幾個寫法你應該了解了吧

test.erase(test_iterator++);
這種寫法會把指針test_iterator指向下一個元素的下一個元素
test.erase(test_iterator);
test_iterator = test.erase(test_iterator);等效的
你可以
for(vector ::iterator test_iterator = test.begin(); test_iterator != test.end()😉
{
if( test_iterator == 3) {
test_iterator = test.erase(test_iterator);
}
else {
test_iterator++;
}
}
也可以
for(vector ::iterator test_iterator = test.begin(); test_iterator != test.end()😉
{
if(
test_iterator == 3) {
test.erase(test_iterator);
}
else {
test_iterator++;
}
}

總結

1.iterator只不過是一個內置類型,類似智能指針對元素訪問進行封裝
2.erase的操作是由后向前賦值的過程
3.每次erase之后析構的是最后的無用元素
4.erase的傳入參數和返回值相同

也正因為,erase是對刪除元素和結尾元素進行整理的遷移賦值,導致vector插入刪除的效率低下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM