在C++编程中,std::vector是一个非常强大的容器,用于存储动态数组。然而,使用std::vector时需要注意的一个重要方面是其迭代器的行为。当vector的容量发生变化时(例如添加或删除元素),迭代器可能会失效,这可能导致程序错误。
迭代器失效的情况:
1、重新分配内存
当向vector添加新元素导致其容量不足时,vector会自动扩展以容纳更多的元素。这种情况下,所有指向旧内存区域的迭代器、指针和引用都会失效。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin(); // 指向第一个元素
// 添加一个元素可能触发重新分配
vec.push_back(4);
if (it != vec.end()) {
std::cout << "Element: " << *it << std::endl;
} else {
std::cout << "Iterator is invalid." << std::endl;
}
return 0;
}
在这个例子中,如果push_back()操作触发了重新分配,则it迭代器将变得无效,尝试访问它会导致未定义行为。
2、删除元素
从vector中移除元素后,该位置之后的所有元素及其对应的迭代器都会受到影响。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.erase(vec.begin()); // 删除第一个元素
while (it != vec.end()) {
std::cout << *it << " ";
++it;
}
std::cout << std::endl;
return 0;
}
这里,erase()返回的是下一个有效元素的迭代器,确保了循环的安全性。
3、使用 clear() 方法清空容器
当调用clear()时,所有元素都会被删除,但已分配的内存空间不会被释放。这意味着容器的容量保持不变,但所有指向原元素的迭代器、指针或引用都将变得无效。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin(); // 获取开始位置的迭代器
std::cout << "Before clear: Capacity = " << vec.capacity() << ", Size = " << vec.size() << std::endl;
vec.clear(); // 清空向量
std::cout << "After clear: Capacity = " << vec.capacity() << ", Size = " << vec.size() << std::endl;
if (it != vec.end()) {
// 这里会输出错误信息,因为it已经失效
std::cout << "Element: " << *it << std::endl;
} else {
std::cout << "Iterator is invalid after calling clear()." << std::endl;
}
return 0;
}
调用clear()后,迭代器it仍然指向原来的内存位置,但由于这些位置上的元素已被删除,所以it实际上是无效的。为了安全起见,应该在clear()之后重新获取迭代器。
4、使用 resize() 改变大小
减小尺寸:如果通过resize()减小vector的尺寸,那么超出新尺寸范围的元素会被删除,相应的迭代器也会失效。
增大尺寸:如果增加尺寸,则新增加的位置上的迭代器是未定义的(直到它们被初始化)。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin() + 2; // 指向第三个元素
std::cout << "Before resize: Capacity = " << vec.capacity() << ", Size = " << vec.size() << std::endl;
std::cout << "Element at it before resize: " << *it << std::endl;
vec.resize(2); // 减小尺寸到2
std::cout << "After resizing to 2: Capacity = " << vec.capacity() << ", Size = " << vec.size() << std::endl;
if (it != vec.end()) {
// 这里可能会导致未定义行为,因为it已经失效
std::cout << "Element at it after resize: " << *it << std::endl;
} else {
std::cout << "Iterator is invalid after resizing to 2." << std::endl;
}
vec.resize(6, 0); // 增大尺寸到6,并用0填充
it = vec.begin() + 2; // 重新获取有效迭代器
std::cout << "After resizing to 6: Capacity = " << vec.capacity() << ", Size = " << vec.size() << std::endl;
std::cout << "Element at it after resizing to 6: " << *it << std::endl;
return 0;
}
5、使用 swap() 交换两个 vector 的内容
当两个vector之间进行数据交换时,它们各自的迭代器、指针和引用都将指向对方的数据结构。这意味着原来的迭代器将不再指向原来的数据。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = {4, 5, 6};
auto it1 = vec1.begin();
auto it2 = vec2.begin();
std::cout << "Before swap: vec1[0] = " << *it1 << ", vec2[0] = " << *it2 << std::endl;
vec1.swap(vec2); // 交换两个向量的内容
std::cout << "After swap: vec1[0] = " << *it1 << ", vec2[0] = " << *it2 << std::endl;
// 输出显示迭代器现在指向了交换后的数据
std::cout << "it1 now points to: " << *it1 << std::endl;
std::cout << "it2 now points to: " << *it2 << std::endl;
return 0;
}
6、使用 assign() 重新赋值
assign()可以用来替换vector中的所有元素,这个过程类似于先调用clear()再插入新元素。因此,任何之前存在的迭代器都会失效。
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin();
std::cout << "Before assign: Element at it = " << *it << std::endl;
vec.assign(3, 0); // 用3个0替换原有元素
if (it != vec.end()) {
// 这里会导致未定义行为,因为it已经失效
std::cout << "After assign: Element at it = " << *it << std::endl;
} else {
std::cout << "Iterator is invalid after assign." << std::endl;
}
// 重新获取有效的迭代器
it = vec.begin();
std::cout << "New element at it: " << *it << std::endl;
return 0;
}
vector删除经典错误
当从vector中删除一个元素时,所有指向该元素及其之后元素的迭代器都会失效。如果继续使用这些迭代器,会导致未定义行为。
下面这个是新手最容易犯的一个错误:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it == 3) {
vec.erase(it); // 删除元素3
}
std::cout << *it << " "; // 继续使用失效的迭代器
}
std::cout << std::endl;
return 0;
}
当*it == 3时,调用vec.erase(it)会删除元素3。
erase()返回的是下一个有效元素的迭代器,但在这个例子中,it并没有更新为新的迭代器,而是继续递增,导致访问已失效的迭代器。
正确的做法是:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ) {
if (*it == 3) {
it = vec.erase(it); // 更新迭代器
} else {
++it; // 只有在没有删除元素的情况下才递增迭代器
}
}
for (const auto &val : vec) {
std::cout << val << " ";
}
std::cout << std::endl;
return 0;
}
总结:
为了避免由于迭代器失效引起的错误,我们在使用迭代器遍历vector时,要避免在可能使迭代器失效的操作前后更新迭代器,或者尽量减少对vector的修改操作直到遍历完成。此外,使用基于范围的for循环(如for (auto &elem : vec))可以避免直接管理迭代器,从而简化代码并减少出错的机会。