【C++ STL】迭代器失效的幾種情況總結


迭代器的失效問題:對容器的操作影響了元素的存放位置,稱為迭代器失效。

失效情況:

  • 當容器調用erase()方法后,當前位置到容器末尾元素的所有迭代器全部失效。
  • 當容器調用insert()方法后,當前位置到容器末尾元素的所有迭代器全部失效。
  • 如果容器擴容,在其他地方重新又開辟了一塊內存。原來容器底層的內存上所保存的迭代器全都失效了。

一、序列式容器

序列式容器(如 vector, deque)的迭代器失效示例如下:

#include<iostream>
#include<vector>

using namespace std;

int main() {

	vector<int> q{ 1,2,3,4,5,6 };
	// 在這里想把大於2的元素都刪除
	for (auto it = q.begin(); it != q.end(); it++) {
		if (*it > 2)
			q.erase(it); // 這里就會發生迭代器失效
	}
	// 打印結果
	for (auto it = q.begin(); it != q.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	return 0;
}

使用 VS2019 運行什么也沒打印就退出了進程。調試發現在 vector 源碼內的_Vector_const_iterator& operator++()引發了異常:讀取訪問權限沖突,表明迭代器在執行++操作時報錯,因為已經失效的迭代器不能再進行自增運算了。

迭代器失效的原因是:因為 vetor、deque 使用了連續分配的內存,erase操作刪除一個元素導致后面所有的元素都會向前移動一個位置,這些元素的地址發生了變化,所以當前位置到容器末尾元素的所有迭代器全部失效。

解決方法是利用erase方法可以返回下一個有效的 iterator,所以代碼做如下修改即可:

// 在這里想把大於2的元素都刪除
for(auto it=q.begin();it!=q.end();)
{
    if(*it>2)
    {
    	it=q.erase(it); // 這里會返回指向下一個元素的迭代器,因此不需要再自加了
    }
    else
    {
    	it++;
    }
}

二、鏈表式容器

對於鏈表式容器(如 list),刪除當前的 iterator,僅僅會使當前的 iterator 失效,這是因為 list 之類的容器,使用了鏈表來實現,插入、刪除一個結點不會對其他結點造成影響。只要在 erase 時,遞增當前 iterator 即可,並且 erase 方法可以返回下一個有效的 iterator。

方式一:遞增當前 iterator

for (iter = cont.begin(); it != cont.end();)
{
   (*iter)->doSomething();
   if (shouldDelete(*iter))
      cont.erase(iter++);
   else
      iter++;
}

方式二:通過 erase 獲得下一個有效的 iterator

for (iter = cont.begin(); iter != cont.end();)
{
   (*it)->doSomething();
   if (shouldDelete(*iter))
      iter = cont.erase(iter);  //erase刪除元素,返回下一個迭代器
   else
      ++iter;
}

三、關聯式容器

對於關聯容器(如 map, set,multimap,multiset),刪除當前的 iterator,僅僅會使當前的 iterator 失效,只要在 erase 時,遞增當前 iterator 即可。這是因為 map 之類的容器,使用了紅黑樹來實現,插入、刪除一個結點不會對其他結點造成影響。erase 迭代器只是被刪元素的迭代器失效,但是返回值為 void,所以要采用erase(iter++)的方式刪除迭代器。

for (iter = cont.begin(); it != cont.end();)
{
   (*iter)->doSomething();
   if (shouldDelete(*iter))
      cont.erase(iter++);
   else
      ++iter;
}

//測試錯誤的Map刪除元素
void mapTest()
{
    map<int, string> dataMap;


    for (int i = 0; i < 100; i++)
    {
           string strValue = "Hello, World";

            stringstream ss;
            ss<<i;
            string tmpStrCount;
            ss>>tmpStrCount;
            strValue += tmpStrCount;
            dataMap.insert(make_pair(i, strValue));
    }

    cout<<"MAP元素內容為:"<<endl;
     map<int, string>::iterator iter;
    for (iter = dataMap.begin(); iter != dataMap.end(); iter++)
    {
            int nKey = iter->first;
            string strValue = iter->second;
            cout<<strValue<<endl;
    }

    cout<<"內容開始刪除:"<<endl;
    //刪除操作引發迭代器失效
    for (iter = dataMap.begin(); iter != dataMap.end();iter++)
    {
            int nKey = iter->first;
            string strValue = iter->second;

           if (nKey % 2 == 0)
           {
                dataMap.erase(iter);    //錯誤

           }
           /* cout<<iter->second<<endl;*/
    }
}

解析:dataMap.erase(iter)之后,iter 就已經失效了,所以 iter 無法自增,即iter++就會出bug。解決方案,就是在 iter 失效之前,先自增。

void mapTest()
{
    map<int, string> dataMap;


    for (int i = 0; i < 100; i++)
    {
           string strValue = "Hello, World";

            stringstream ss;
            ss<<i;
            string tmpStrCount;
            ss>>tmpStrCount;
            strValue += tmpStrCount;
            dataMap.insert(make_pair(i, strValue));
    }

    cout<<"MAP元素內容為:"<<endl;
    map<int, string>::iterator iter;
    for (iter = dataMap.begin(); iter != dataMap.end(); iter++)
    {
            int nKey = iter->first;
            string strValue = iter->second;
            cout<<strValue<<endl;
    }

    cout<<"內容開始刪除:"<<endl;
    for (iter = dataMap.begin(); iter != dataMap.end();)
    {
            int nKey = iter->first;
            string strValue = iter->second;

           if (nKey % 2 == 0)
           {
                dataMap.erase(iter++);
                auto a = iter;

           }
           else {
               iter ++;
           }
    }
}

解析:dataMap.erase(iter++);這句話分三步走,先把 iter 傳值到 erase 里面,然后 iter 自增,然后執行 erase,所以 iter 在失效前已經自增了。

map 是關聯容器,以紅黑樹或者平衡二叉樹組織數據,雖然刪除了一個元素,整棵樹也會調整,以符合紅黑樹或者二叉樹的規范,但是單個節點在內存中的地址沒有變化,變化的是各節點之間的指向關系。

所以在 map 中為了防止迭代器失效,在有刪除操作時,常用如下方法:

for (iter = dataMap.begin(); iter != dataMap.end(); )
{
         int nKey = iter->first;
         string strValue = iter->second;

         if (nKey % 2 == 0)
         {
               map<int, string>::iterator tmpIter = iter;
           iter++;
               dataMap.erase(tmpIter);
               //dataMap.erase(iter++) 這樣也行

         }else
     {
          iter++;
         }
}

參考:

【C++知識】關於迭代器失效的幾種情況

C++迭代器失效的幾種情況總結

C++迭代器二:詳解迭代器失效的底層核心原理



免責聲明!

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



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