1. 程式人生 > 其它 >迭代器失效問題

迭代器失效問題

話說回來,今天 ymx 發了個關於 std::list 迭代器失效的問題,正好也讓我對這方面的芝士有所瞭解,故作文以記之((

首先先貼一張 C++ reference 裡的圖:

我們知道 C++ STL 中的容器大致可分為四類,序列型(如 vector,deque,queue)、關聯型(set,map,multiset)和連結串列型(list,forward_list),這三種類型的容器迭代器失效的情況是不同的,因此這裡會對其一一進行闡述:

1. 序列型容器

序列型容器的特點是它記憶體分配是一段連續的區間,也就是說,當你刪除一個元素後會導致後面的元素全部向前移動一格,使得後面元素的迭代器全部失效

,因此刪除 vector 中的元素不能這麼寫:

for(vector<int>::iterator it=vec.begin();it!=vec.end();it++){
    if((*it)>100) vec.erase(it);
}

也不能這麼寫:

for(vector<int>::iterator it=vec.begin();it!=vec.end();it++){
    if((*it)>100) vec.erase(it++);
}

一個解決辦法是:利用 vector 型別中 erase 函式的返回值找出下一個合法的地址,即:

for(vector<int>::iterator it=vec.begin();it!=vec.end();){
    if((*it)>100) it=vec.erase(it);
    else ++it;
}

此外,根據 vector 自動伸長的原理(如果不清楚參見 THUSC2021 試機賽 T2,如果沒參加過 THUSC2021……那恐怕我也沒辦法了),若刪除一個元素後 vector 的長度變為某個 \(2^k-1(k\in\mathbb{Z})\),那麼 vector 分配的記憶體大小將會減半,導致 vector 內部全部重構,此時整個 vector 的迭代器都會失效。

2. 關聯型容器

與序列型容器不同的是,關聯型容器內部是二叉樹或紅黑樹,因此它們的記憶體分配是沒有規律的,刪除一個元素後只會對使被刪除的元素的迭代器失效。但是注意,失效後的迭代器你對它進行任何操作都將會導致 RE,因此你不能這樣刪除元素:

for(set<int>::iterator it=st.begin();it!=st.end();it++){
    if((*it)>100) st.erase(it);
}

原因是你對失效的迭代器進行自增操作。
而這樣寫就不會出現問題:

for(set<int>::iterator it=st.begin();it!=st.end();it++){
    if((*it)>100) st.erase(it++);
}

值得注意的一點是,在 std::set 型別中,erase 函式是沒有返回值的,因此也就不能通過呼叫 erase 函式獲得下一個有效地址的位置,否則會獲得 CE 的好成績(London Fog

3. 連結串列型容器

對於連結串列型容器而言,雖然從外表上看起來它使用了陣列型的結構,但實際上它記憶體的分佈也是不連續的,因此刪除一個元素並不會對其他元素的地址產生影響,因此可以通過 erase 函式的返回值獲得下一個有效地址,也可通過在刪除的同時自增迭代器的方式避免迭代器失效