C++ vector 基本操作
1. 引入 vector
vector
是C++标准库中的一种容器,用于实现动态数组,能够自动调整其大小。使用前需要包含 <vector>
头文件。
#include <vector>
using namespace std;
2. 初始化与构造
构造方法 | 描述 |
---|---|
vector<T>() | 创建一个空的 vector |
vector<T>(size_type n) | 创建含有 n 个元素的 vector,元素默认初始化 |
vector<T>(size_type n, const T& value) | 创建含有 n 个元素的 vector,元素初始化为 value |
vector<T>(const initializer_list<T>& il) | 从一个 initializer_list 构造 vector |
vector<T>(InputIt first, InputIt last) | 从迭代器区间 [first, last) 构造 vector |
示例:
vector<int> v1; // 空 vector
vector<int> v2(5); // 5个默认初始化的 int 元素
vector<int> v3(5, 42); // 5个元素,每个元素都是 42
vector<int> v4{1, 2, 3}; // 从初始化列表构造
vector<int> v5(v4.begin(), v4.end()); // 从迭代器区间构造
3. 访问元素
方法 | 描述 |
---|---|
operator[] | 快速访问元素,不安全 |
at() | 安全访问元素,越界时抛出异常 |
front() | 获取第一个元素 |
back() | 获取最后一个元素 |
示例:
int first = v4.front(); // 第一个元素
int last = v4.back(); // 最后一个元素
int second = v4.at(1); // 安全访问第二个元素
4. 容量与大小
方法 | 描述 |
---|---|
size() | 返回当前元素数量 |
empty() | 判断 vector 是否为空 |
capacity() | 返回当前容量 |
reserve(size_type n) | 请求预留足够空间 |
shrink_to_fit() | 尝试减小容量 |
流程图示例:
5. 修改操作
方法 | 描述 |
---|---|
push_back(value) | 在末尾添加一个元素 |
pop_back() | 移除最后一个元素 |
clear() | 清空所有元素 |
insert(pos, value) | 在 pos 插入元素 |
erase(pos) | 删除 pos 位置的元素 |
resize(size, value) | 改变元素数量,可指定填充值 |
示例:
v4.push_back(99); // 在末尾添加一个元素
v4.pop_back(); // 移除最后一个元素
v4.clear(); // 清空所有元素
v4.insert(v4.begin(), 0); // 在开始处插入一个元素
v4.erase(v4.begin()+1); // 删除第二个元素
v4.resize(10, -1); // 调整大小为10,不足部分填充-1
6. 迭代器
方法 | 描述 |
---|---|
begin() | 返回指向第一个元素的迭代器 |
end() | 返回指向最后一个元素之后位置的迭代器 |
rbegin() | 返回指向最后一个元素的逆向迭代器 |
rend() | 返回指向第一个元素之前位置的逆向迭代器 |
示例:
for (auto it = v4.begin(); it != v4.end(); ++it) {
cout << *it << ' ';
}
cout << endl;
7. 内存管理与性能考虑
vector
在内部使用连续内存块存储数据,这使得随机访问非常高效。当 vector
的容量不足以容纳新元素时,它会重新分配更大的内存块并复制现有元素,通常新容量是原容量的两倍,这一过程可能带来较大的性能开销。
-
扩容策略:当
vector
需要更多空间时,它通常会申请比当前容量大得多的空间。这种策略有助于减少频繁的重新分配,但可能会导致内存使用效率降低。 -
性能影响:由于
vector
的这种内部实现,插入和删除操作在非尾部位置执行时,会导致大量元素的移动,从而产生较高的时间复杂度。因此,在已知元素数量的情况下,预先调用reserve
可以显著提高性能。
8. 实例演示:动态数组的使用
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> numbers; // 创建空 vector
// 向 vector 添加元素
for (int i = 0; i < 10; ++i) {
numbers.push_back(i);
}
// 使用迭代器遍历 vector
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
cout << *it << " ";
}
//--> 0 1 2 3 4 5 6 7 8 9
cout << endl;
// 使用 range-based for 循环遍历 vector
for (const auto& num : numbers) {
cout << num << " ";
}
//--> 0 1 2 3 4 5 6 7 8 9
cout << endl;
// 使用 at() 方法访问元素
try {
cout << numbers.at(5) << endl;
}
catch (out_of_range& e) {
cerr << "Error: " << e.what() << endl;
}
//--> 5
return 0;
}
9. 性能优化技巧
- 预分配内存:使用
reserve
函数提前分配足够的内存,避免多次重新分配。 - 避免不必要的拷贝:尽可能使用
emplace
或emplace_back
来在容器中直接构造对象,这样可以避免不必要的拷贝或移动。 - 使用迭代器:在遍历或修改容器时,优先使用迭代器,这样可以避免数组下标访问的边界检查开销。
- 适时使用
shrink_to_fit
:在不再需要额外内存时,调用此函数释放未使用的内存,但这不应过度使用,因为它可能导致性能下降。
10. 常见问题解答
Q: 如何在 vector
中查找特定元素?
- A: 可以使用算法如
find
或者自定义循环来查找元素。
#include <iostream>
#include <vector>
#include <algorithm> // 包含 find
using namespace std;
int main() {
vector<int> numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int target = 5; // 指定要查找的元素
// 使用 find 查找目标元素的位置
auto rit = find(numbers.begin(), numbers.end(), target);
if (rit != numbers.end())
// 如果找到元素,打印其位置(基于0的索引)
cout << "找到元素的位置: " << distance(numbers.begin(), rit) << endl;
//--> 找到元素的位置:4
else
// 如果没有找到元素
cout << "在向量中未找到元素。" << endl;
return 0;
}
Q: 如何在 vector
中插入元素而不改变其迭代器的有效性?
-
A: 如果你希望插入元素后,原有的迭代器仍然有效,可以尝试以下两种方法之一:
- 在向量的末尾插入元素。使用
push_back
或emplace_back
,它们不会使先前存在的迭代器失效。 - 在向量中某个已知位置之后插入元素。例如,如果你知道元素将被插入在向量的末尾或某个固定位置之后,那么插入操作不会使之前的迭代器失效。
vector<int> vec = {1, 2, 3}; auto it = vec.begin(); // 在末尾插入元素,之前的迭代器仍然有效 vec.push_back(4); // 在已知位置之后插入元素,之前的迭代器仍然有效 vec.insert(it + 2, 99);
- 在向量的末尾插入元素。使用
Q: vector
中的 reserve
和 resize
有何区别?
-
A:
reserve
和resize
都用于改变vector
的大小,但它们的工作方式和用途有所不同:reserve
:仅请求额外的内存空间,但不会改变向量的大小或其元素数量。它的主要目的是为了避免频繁的重新分配,提高性能。resize
:改变向量的大小,如果新大小大于当前大小,将用指定的值填充剩余的空间;如果新大小小于当前大小,则会截断多余的元素。
vector<int> vec; // 预留足够的内存空间存储100个元素,但实际元素数量仍为0 vec.reserve(100); // 将向量大小调整为10,不足的部分用0填充 vec.resize(10, 0);
Q: 迭代器失效问题
错误示例(插入与删除)
考虑一个std::vector
,直接在遍历中进行插入或删除操作将导致迭代器失效:
vector<int> vec = {1, 2, 3, 4};
for (auto it = vec.begin(); it != vec.end(); ++it) {
if (*it == 2) {
vec.insert(it, 99); // 错误:插入操作使迭代器失效
}
if (*it == 3) {
vec.erase(it); // 错误:删除操作使迭代器失效
}
}
问题分析:插入或删除元素会导致容器内部结构发生变化,使得迭代器指向的位置不再有效。
正确用法(更新迭代器)
应当使用insert
和erase
函数返回的迭代器来更新当前迭代器,以避免失效问题:
vector<int> vec = {1, 2, 3, 4};
for (auto it = vec.begin(); it != vec.end(); ) {
if (*it == 2) {
// 在循环条件后立即更新迭代器,确保迭代器始终指向容器中的有效位置。
it = vec.insert(it, 99);
}
if (*it == 3) {
it = vec.erase(it);
} else {
++it;
}
}
错误示例(容量变化)
在容器上执行可能改变其容量的操作,如push_back
,也可能导致迭代器失效:
vector<int> vec = {1, 2, 3, 4};
auto it = vec.begin();
vec.push_back(5); // 可能导致重新分配内存,使所有迭代器失效
问题分析:当容器需要重新分配内存时,所有现有迭代器将失效。
正确用法(检查迭代器有效性)
在执行可能导致容器重新分配内存的操作后,应检查并重新获取迭代器:
vector<int> vec = {1, 2, 3, 4};
auto it = vec.begin();
vec.push_back(5); // 可能导致重新分配内存
if (it != vec.end()) {
// 迭代器仍然有效,可以继续使用
} else {
// 迭代器失效,重新获取迭代器
it = vec.begin();
}
关键点:通过比较迭代器与end()
来判断其有效性,并在必要时重新初始化迭代器。