话说回来,今天 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(kinmathbb{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
函数的返回值获得下一个有效地址,也可通过在删除的同时自增迭代器的方式避免迭代器失效