迭代器失效問題
話說回來,今天 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
函式的返回值獲得下一個有效地址,也可通過在刪除的同時自增迭代器的方式避免迭代器失效