c++之迭代器失效

來源:互聯網
上載者:User

標籤:特性   判斷   return   main   before   show   讀者   stdio.h   程式   

 

1.首先從一到題目開始談說起迭代器失效。有時我們很自然並且自信地 用下面方法刪除vector元素:

#include <iostream>
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <string>

void
del_elem(vector<string> &vec, const char * elem){ vector<string>::iterator itor = vec.begin(); for (; itor != vec.end(); itor++) { if (*itor == elem) { vec.erase(itor); } }}template <class InputIterator>void show_vec(InputIterator first, InputIterator last){ while(first != last) { std::cout << *first << " "; first++; } std::cout << " " << std::endl;}intmain(void){ string arr[] = {"php", "c#", "java", "js", "lua"}; vector<string> vec(arr, arr+(sizeof(arr)/sizeof(arr[0]))); std::cout << "before del: " << std::endl; show_vec(vec.begin(), vec.end()); del_elem(vec, "php"); std::cout << "after del: " << std::endl; show_vec(vec.begin(), vec.end()); return 0;}

  當 string arr[] = {"php", "c#", "java", "js", "lua"}; 時,運行上邊程式,得到如下輸出:

    

  運行結果是正確的啊。 找到 "php" ,然後刪除,剩下四個元素。 

  但是實際上 del_elem 的過程是和我們想象的不一樣的,在 del_elem中列印下每一步的 itor 的值,就會發現蛛絲馬跡。

  將 del_elem加上log:

  

void del_elem(vector<string> &vec, const char * elem){    std::cout << "----------------------------" << std::endl;        vector<string>::iterator itor = vec.begin();    for (; itor != vec.end(); itor++)    {        std::cout << *itor << std::endl;        if (*itor == elem)        {            vec.erase(itor);        }    }    std::cout << "----------------------------" << std::endl;}

  我們在做刪除操作前,列印每個元素的值, 繼續編譯運行得到如下結果:

    

  在做 del_elem操作時,少列印了一個 "c#", 也就是在列印完"php",然後刪除php以後,接下來列印的不是 "c#", 而直接列印了 "java" 。

      那麼我們可以將 vec.erase(itor) 注釋掉,然後 可以得到 del_elem 會列印所有的元素值,

  如此看來 c# 是因為執行了erase 操作以後,“變沒了”。

  弄清這個問題,我們要看看一組vector操作的定義:

iterator erase(iterator position){    if(position + 1 != end())        copy(position + 1, finish, position);    --finish;    destroy(finish);    return position;}

iterator begin() { return start; }

iterator end() { return finish; }

 

   我們經常使用  vec.begin(), vec.end(), 想必也能知道start和finish 為何物。

  首先看erase函數: 先判斷 待刪除的迭代器是否為 finish 的前一個元素,也就是vector中的最後一個元素,

        如果不是最後一個元素,就將待刪除迭代器的後邊所有元素往前移動一個slot, 然後 --finish  重新置放finish指標。

    此時finish指標指向的是沒刪除之前的最後一個元素地址,本例中就是 lua的地址, 但是當前的finish指標處的值已經沒用了,於是調用destroy。

    如果待刪除迭代器是finish的前一個元素的話,那麼就直接移動finish指標,調用destroy銷毀該位置處的元素對象。

    與此同時,我們看到erase函數傳進來的迭代器,只起到了一個位置定位判斷的作用,erase本身沒有對該迭代器有任何操作,該迭代器的值所在地址仍然有效,但是由於進行了copy操作,position處的值已經變成了"c#".

    再回過頭來看一下我們的 del_elem 函數:

      當刪除第一個元素php之後,在執行 itor++之前,php之後的所有元素都前移了一個slot導致此時 itor存放的元素已經是 c#,

      於是繼續執行itor++後,此時itor又向後移動,itor中的值已經是java,c#就是這樣被漏掉的。

 

  1-2 由此,又可以得出另一個結論,當arr中有n(n>=2)個 連續的php元素,我們用 del_elem函數 是不能刪除掉所有的php元素的,於是這樣就會導致bug。

  我們將 string arr[] = {"php", "c#", "java", "js", "lua"}; 改為 string arr[] = {"php", "php", "php", "php", "c#", "java", "js", "lua"}; 後,觀察運行結果:

    

  固然不出所料,php沒有被刪除乾淨,因為當刪除第一個php以後,用當前 del_elem 方法,總是會漏掉刪除的php之後的元素,如果這個元素恰好是 "php",便會出現bug。

  1-3 用當前 del_elem刪除一個元素,會導致 finish 前移一個slot,如果將php放到最後slot,即finish之前的slot中,當刪除最後一個php後,finish會指向刪除的php的地址(已經非法了),

    然後php的地方會被銷毀,然後又執行 itor++,於是此時的itor指向到finish的後邊,當 判斷 itor != vec.end() 時,這個式子是成立的,於是繼續執行,但是當對迭代器解引用時,最終會由於是非法

    引用地址,程式崩掉。我們來看一下是否是這樣, 將最後一個元素改為 "php"; string arr[] = {"php", "php", "php", "php", "c#", "java", "js", "php"};

    編譯運行,結果如下:

    

     gdb調試,發現是因為 *itor 導致程式崩潰。

2 以上例子指出了vector 刪除元素時選擇的方法不當導致的一些問題;

  1 是刪除多個相同元素時,因為vector自身的特性導致 刪除不淨,出現bug

  2 是當刪除的元素時最後一個元素,可能導致程式崩潰。

3 我們很多時候都知道vector 迭代器失效會出問題,但是很多時候不知道會導致什麼問題。

   以上例子列舉了 迭代器失效的 結果, 那麼反過來 ,我們再研究 “什麼是vector刪除元素會導致迭代器失效” 的問題。  

   我的結論是,在對vector進行刪除元素的時候, 刪除元素之前,假設我們定義了一些迭代器分別指向,

    1 迭代器的位置位於 待刪除迭代器之前,

    2 待刪除的迭代器

    3 迭代器的位置位於 待刪除的迭代器之後

   那麼當對待刪除的迭代器調用erase(itor)以後,之前定義在itor之前的迭代器依舊有用, 之前定義的 itor 以及 itor之後的迭代器 已經失效了,這裡的失效是指,這些迭代器所指的元素內容已經和刪除之前的不一樣了,甚至可能是指向了非法地址。

  於是在對這些失效的迭代器進行操作的時候 可能導致程式出bug ,或者直接崩潰。

4. 那麼該如何刪除vector元素呢?

  可以參考:

void del_elem(vector<string> &vec, const char *elem){    vector<string>::iterator itor = vec.begin();    for (; itor != vec.end();)    {        std::cout << *itor << " " << &(*itor) << std::endl;        if (*itor == elem)        {            itor = vec.erase(itor);                 //個人覺得這句賦值是多餘的,因為erase本身沒有對itor進行任何操作,erase操作之前和操作之後的itor所指向的位置是不變的,變的只是裡邊的值。 如有理解錯誤,還望及時指出        }        else        {            itor++;        }    }}

5. 想必讀者已經對vector刪除元素引起的迭代器失效有了一些理解,那麼再來理解插入元素導致的迭代器失效會更容易一些。

  1 如果插入操作引起了空間重新設定,(申請新空間,賦舊空間的值到新空間,釋放舊空間),那麼在插入操作執行後,之前聲明的所有迭代器都將失效

  2 如果沒有引起空間配置,那麼會導致插入位置之後的迭代器失效。

6. 我們 假如我們聲明了一些迭代器,對vector進行了插入或刪除操作以後,要注意這些迭代器可能已經失效。

 

7. 由vector的迭代器失效,可以引出,其他序列式容器的迭代器失效,其他關聯式容器的迭代器失效。內容太多,本篇只是先給出vector的迭代器失效的一些理解,後續繼續補充其它的。

   <effective stl> 第九條,較詳細的討論的各種容器的操作方法,有興趣的讀者可自行翻閱。

 

水平有限,錯誤難免,望及時指出。希望能對大家理解迭代器失效提供一些思路。

  

  

 

  

  

 

c++之迭代器失效

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.