C++ Vector 教程:掌握动态数组的强大功能
在 C++ 编程中,std::vector 是 C++ 标准模板库 (STL) 中一个极其重要且广泛使用的容器。它提供了一个动态数组的功能,是传统 C 风格数组的强大且灵活的替代品。本文将深入探讨 std::vector 的各个方面,帮助您全面掌握其功能。
什么是 C++ std::vector?
std::vector 是一种序列容器,它将元素存储在连续的内存位置,这与 C 风格数组类似。然而,其核心区别在于它的“动态”特性:与固定大小的数组不同,std::vector 可以根据需要自动调整大小,以容纳新元素或在元素被移除时收缩。这种动态调整大小的能力使得 std::vector 在处理那些在编译时无法确定大小或在程序执行期间大小会发生变化的数据集合时非常灵活和强大。
为何选择使用 std::vector?
在许多情况下,std::vector 比传统数组更受欢迎,原因在于其显著的优势:
- 动态大小:
std::vector可以根据需要增长或收缩,消除了手动内存管理和重新分配的繁琐。这使得代码更简洁,并减少了内存泄漏的风险。 - 易用性: 它提供了一组丰富的成员函数,用于常见的操作,如添加、删除、访问和遍历元素。这简化了数据集合的操作。
- 效率: 元素在内存中连续存储,因此可以通过索引进行高效的随机访问,就像数组一样。在向量末尾插入和删除元素通常具有摊销常数时间复杂度。
- STL 集成: 作为 STL 的一部分,
std::vector与 STL 中的算法和其他容器无缝集成,提供了强大的功能组合。
std::vector 与 C 风格数组的关键区别
| 特性 | std::vector |
C 风格数组 |
|---|---|---|
| 大小 | 动态;可在运行时更改。 | 固定;大小必须在编译时确定。 |
| 内存管理 | 自动内存管理,无需手动 new/delete。 |
手动内存管理,容易出错。 |
| 安全性 | at() 函数提供边界检查,越界会抛出异常。 |
不提供边界检查,越界访问可能导致未定义行为。 |
| 灵活性 | 更适合频繁地添加/删除元素。 | 更适合频繁地元素访问。 |
| 数据类型 | 可以存储各种对象类型。 | 通常存储同质元素。 |
如何使用 C++ std::vector
要使用 std::vector,您需要包含 <vector> 头文件。
1. 声明和初始化
std::vector 可以通过多种方式声明和初始化:
- 空向量:
cpp
#include <vector>
std::vector<int> myVector; // 声明一个存储 int 类型的空向量 - 使用初始化列表:
cpp
std::vector<int> myVector = {1, 2, 3, 4, 5}; // 初始化并填充元素 - 统一初始化(C++11 及更高版本):
cpp
std::vector<int> myVector {1, 2, 3, 4, 5}; - 指定大小和默认值:
cpp
std::vector<int> myVector(5, 0); // 声明一个包含 5 个整数的向量,所有元素初始化为 0 - 从另一个向量或数组初始化:
cpp
std::vector<int> otherVector = {10, 20, 30};
std::vector<int> newVector(otherVector.begin(), otherVector.end()); // 从另一个向量复制
2. 添加元素
push_back(): 将元素添加到向量的末尾。这是最常用的添加元素的方式。
cpp
std::vector<int> numbers;
numbers.push_back(10); // numbers: {10}
numbers.push_back(20); // numbers: {10, 20}insert(): 在特定位置插入元素。对于大型向量,这可能效率较低,因为它可能需要移动后续元素。
cpp
std::vector<int> numbers = {1, 2, 3};
numbers.insert(numbers.begin() + 1, 100); // numbers: {1, 100, 2, 3}
3. 访问元素
元素可以通过它们的索引(基于 0)进行访问:
[]运算符:
cpp
int firstElement = myVector[0];at()函数:
cpp
int secondElement = myVector.at(1);
at()函数提供了边界检查,如果索引超出范围,它会抛出std::out_of_range异常,这比直接使用[]运算符更安全。
4. 修改元素
可以使用 [] 运算符或 at() 函数更改元素:
cpp
std::vector<int> myVector = {1, 2, 3};
myVector[0] = 10; // myVector: {10, 2, 3}
myVector.at(1) = 20; // myVector: {10, 20, 3}
5. 删除元素
pop_back(): 从向量末尾删除最后一个元素。
cpp
std::vector<int> numbers = {1, 2, 3};
numbers.pop_back(); // numbers: {1, 2}clear(): 删除向量中的所有元素。
cpp
myVector.clear(); // myVector 变为空erase(): 从特定位置或指定范围删除元素。
cpp
std::vector<int> numbers = {1, 2, 3, 4, 5};
numbers.erase(numbers.begin() + 2); // 删除第三个元素 (值 3),numbers: {1, 2, 4, 5}
numbers.erase(numbers.begin() + 1, numbers.begin() + 3); // 删除第二个和第三个元素,numbers: {1, 5}
6. 迭代器
迭代器用于遍历向量的元素:
begin(): 返回指向第一个元素的迭代器。end(): 返回指向最后一个元素“之后”的理论元素的迭代器。
“`cpp
std::vector
// 使用迭代器遍历
for (std::vector
std::cout << *it << ” “;
}
std::cout << std::endl;
// 范围-based for 循环(C++11 及更高版本),更简洁的遍历方式
for (int num : numbers) {
std::cout << num << ” “;
}
std::cout << std::endl;
“`
7. 容量和大小函数
size(): 返回向量中当前元素的数量。capacity(): 返回向量在不重新分配内存的情况下可以容纳的元素总数。capacity()通常大于或等于size()。empty(): 检查向量是否为空(即size()为 0)。resize(n): 改变存储的元素数量。如果n较小,则删除多余元素。如果n较大,则添加新元素并默认初始化。reserve(n): 请求向量的容量至少为n。如果您提前知道大约需要多少元素,这可以防止多次重新分配,从而优化性能。shrink_to_fit(): 将向量的容量减少到恰好适合其当前大小,可能释放未使用的内存。
性能和内存管理
尽管 std::vector 自动处理内存,但理解其内部工作原理对于优化性能至关重要。当向量需要增长超出其当前容量时,它通常会分配一块新的、更大的内存块,将所有现有元素复制到新位置,然后解除分配旧内存。这种重新分配可能是一个昂贵的操作。为了减轻这种情况,向量通常会分配比立即需要更多的内存(其 capacity 大于其 size),以减少重新分配的频率。使用 reserve() 可以通过预先分配足够的内存来帮助优化性能。
高级主题
- 二维向量: 您可以创建向量的向量(例如,
std::vector<std::vector<int>> matrix;)来表示像矩阵这样的二维数据结构。 - 线程安全:
std::vector本身不是线程安全的。如果多个线程同时访问和修改向量,则需要外部同步机制(例如互斥锁)。
通过掌握 std::vector 的这些功能和最佳实践,您将能够更有效地在 C++ 程序中管理动态数据集合,编写出更健壮、更高效的代码。