目录
2.1 vector 的插入操作(push_back, insert)
2.2 vector 的删除操作(erase, pop_back)
1. 什么是迭代器失效?
在 C++ 中,迭代器(iterator) 是一种类似指针的对象,用于遍历 STL 容器(如 vector
、list
、map
等)。
迭代器失效 是指在对容器进行某些操作(如插入、删除)后,原本有效的迭代器变得不可用,继续使用它会导致 未定义行为(Undefined Behavior, UB),如程序崩溃、数据错误等
2. 哪些操作会导致迭代器失效?
不同的容器有不同的迭代器失效规则,本文主要讨论 vector
的迭代器失效问题。
2.1 vector
的插入操作(push_back
, insert
)
当向 vector
插入元素时:
- 如果
size() == capacity()
(容量已满):vector
会重新分配更大的内存,并拷贝原有数据。- 所有迭代器失效(包括
begin()
,end()
等)。
- 如果
size() < capacity()
(容量未满):- 插入点之前的迭代器仍然有效。
- 插入点及之后的迭代器失效(因为元素可能被移动)。
示例:push_back
导致迭代器失效
vector<int> v = {1, 2, 3};
auto it = v.begin(); // it 指向 1
v.push_back(4); // 可能触发重新分配内存
cout << *it; // ❌ 危险!it 可能失效
如何避免?
- 提前预留空间(
reserve()
):
vector<int> v;
v.reserve(100); // 预留 100 个元素的空间
auto it = v.begin();
v.push_back(1); // 不会重新分配,it 仍然有效
- 使用索引代替迭代器(如果允许)。
2.2 vector
的删除操作(erase
, pop_back
)
当从 vector
删除元素时:
- 被删除元素的迭代器失效。
- 被删除元素之后的所有迭代器失效(因为后面的元素会向前移动)。
- 删除点之前的迭代器仍然有效。
示例:erase
导致迭代器失效
vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 2; // it 指向 3
v.erase(v.begin() + 1); // 删除 2
cout << *it; // ❌ 危险!it 已经失效(3 已经前移)
如何正确删除?
- 使用
erase
的返回值(返回下一个有效迭代器):
vector<int> v = {1, 2, 3, 4};
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
it = v.erase(it); // 删除并更新 it
} else {
it++; // 否则正常递增
}
}
反向遍历(避免迭代器失效)
for (auto it = v.rbegin(); it != v.rend(); ) {
if (*it % 2 == 0) {
it = vector<int>::reverse_iterator(v.erase(it.base() - 1));
} else {
it++;
}
}
3. 其他容器的迭代器失效情况
容器 | 插入操作(insert ) | 删除操作(erase ) |
---|---|---|
vector | 可能失效(取决于容量) | 被删除及后面的失效 |
deque | 可能失效(首尾安全) | 被删除及附近的失效 |
list | 不会失效 | 仅被删除的失效 |
map /set | 不会失效 | 仅被删除的失效 |
4. 总结
vector
插入时:- 可能失效(如果触发重新分配)。
- 避免方法:提前
reserve()
或使用索引。
vector
删除时:- 被删除及后面的迭代器失效。
- 正确做法:使用
erase
返回值或反向遍历。
- 其他容器(如
list
、map
)通常更安全,但仍需谨慎。
最佳实践:
- 避免在遍历时直接修改容器,除非明确知道迭代器是否有效。
- 尽量使用
range-based for
或算法(如remove_if
),减少手动管理迭代器。 - 调试时使用
-D_GLIBCXX_DEBUG
(GCC)检测迭代器错误。