C++ Vector 教程 – wiki词典

C++ std::vector 教程:动态数组的强大与灵活

1. 引言

在 C++ 编程中,数组是一种基本的数据结构,用于存储固定大小的同类型元素集合。然而,传统 C 风格数组的一个主要缺点是其大小在编译时必须确定,并且在运行时无法改变。这在许多实际应用中带来了不便和限制。

C++ 标准库提供了一个强大而灵活的容器 std::vector,它克服了传统数组的这些限制。std::vector 是一个能够动态增长和收缩的数组,它在内存中是连续存储的,因此可以像传统数组一样通过索引进行快速访问,同时提供了自动管理内存的便利。它是 C++ 中最常用和最重要的容器之一。

本教程将详细介绍 std::vector 的各个方面,包括其基本概念、声明与初始化、元素操作、容量管理、迭代器以及一些最佳实践。

2. 什么是 std::vector

std::vector 是一个模板类,定义在 <vector> 头文件中。它实现了动态数组的功能,可以在运行时自动调整大小。当你向 vector 中添加元素时,如果其内部存储空间不足,它会自动重新分配更大的内存空间,并将现有元素复制到新位置。

主要特点:
* 动态大小: 可以在运行时改变其大小。
* 连续存储: 元素在内存中是连续存储的,这使得它与传统数组一样支持随机访问,并通过指针算术进行高效遍历。
* 自动内存管理: 负责内存的分配和释放,避免了手动管理内存的复杂性和潜在错误。
* 高效访问: 具有 O(1) 的随机访问时间复杂度(通过索引)。
* 尾部操作高效:vector 尾部添加或删除元素通常是 O(1) 的(均摊)。

3. 声明与初始化

使用 std::vector 之前,需要包含 <vector> 头文件。

“`cpp

include

include

include

include // for std::sort, std::find

int main() {
// 1. 默认构造函数:创建一个空的vector
std::vector vec1; // 类型为int的空vector

// 2. 构造函数带初始大小:创建一个包含N个默认初始化元素的vector
// 对于基本类型(如int),默认初始化为0
std::vector<int> vec2(5); // 包含5个int类型元素,都初始化为0
// 对于类类型,会调用其默认构造函数
std::vector<std::string> vec_str(3); // 包含3个空字符串

// 3. 构造函数带初始大小和初始值:创建一个包含N个指定值的元素
std::vector<int> vec3(5, 100); // 包含5个int类型元素,都初始化为100

// 4. 拷贝构造函数:从另一个vector拷贝
std::vector<int> vec4 = vec3; // vec4是vec3的一个副本

// 5. 范围构造函数:从迭代器指定的一个范围拷贝
std::vector<int> vec5(vec3.begin(), vec3.end()); // vec5是vec3的一个副本

// 6. 初始化列表:C++11引入,最常用、最简洁的初始化方式
std::vector<int> vec6 = {1, 2, 3, 4, 5};
std::vector<std::string> fruits = {"apple", "banana", "cherry"};

std::cout << "vec1 size: " << vec1.size() << std::endl;
std::cout << "vec2 elements: ";
for (int x : vec2) std::cout << x << " ";
std::cout << std::endl;

std::cout << "vec3 elements: ";
for (int x : vec3) std::cout << x << " ";
std::cout << std::endl;

std::cout << "vec6 elements: ";
for (int x : vec6) std::cout << x << " ";
std::cout << std::endl;

return 0;

}
“`

4. 添加与删除元素

4.1 添加元素

  • push_back(const T& val) / push_back(T&& val):在 vector 的末尾添加一个元素。这是最常用的添加元素方法。均摊时间复杂度为 O(1)。
  • emplace_back(Args&&... args):C++11 引入,在 vector 的末尾就地构造一个元素,避免了不必要的拷贝或移动。通常比 push_back 效率更高。均摊时间复杂度为 O(1)。
  • insert(iterator position, const T& val):在指定位置插入一个元素。所有后续元素都需要移动,时间复杂度为 O(N)。
  • insert(iterator position, size_type n, const T& val):在指定位置插入 n 个指定值的元素。
  • insert(iterator position, InputIt first, InputIt last):在指定位置插入一个范围内的元素。
  • insert(iterator position, std::initializer_list<T> il):C++11 引入,在指定位置插入初始化列表中的元素。

“`cpp
// 续 main 函数
std::vector myVec = {10, 20, 30};

// push_back
myVec.push_back(40); // myVec: {10, 20, 30, 40}

// emplace_back (对于int可能看不出优势,对于复杂对象更明显)
myVec.emplace_back(50); // myVec: {10, 20, 30, 40, 50}

// insert 单个元素
myVec.insert(myVec.begin() + 1, 15); // myVec: {10, 15, 20, 30, 40, 50}

// insert 多个相同元素
myVec.insert(myVec.begin() + 3, 2, 25); // myVec: {10, 15, 20, 25, 25, 30, 40, 50}

// insert 范围元素
std::vector<int> anotherVec = {5, 8};
myVec.insert(myVec.begin(), anotherVec.begin(), anotherVec.end()); // myVec: {5, 8, 10, 15, 20, 25, 25, 30, 40, 50}

std::cout << "myVec after additions: ";
for (int x : myVec) std::cout << x << " ";
std::cout << std::endl;

“`

4.2 删除元素

  • pop_back():移除 vector 尾部的一个元素。时间复杂度为 O(1)。
  • erase(iterator position):移除指定位置的元素。所有后续元素需要移动,时间复杂度为 O(N)。
  • erase(iterator first, iterator last):移除指定范围内的元素。所有后续元素需要移动,时间复杂度为 O(N)。
  • clear():移除 vector 中的所有元素,使其变为空。时间复杂度为 O(N)(销毁元素)或 O(1)(对于 POD 类型),但不会释放内存(capacity 不变)。

“`cpp
// 续 main 函数
// pop_back
myVec.pop_back(); // myVec: {5, 8, 10, 15, 20, 25, 25, 30, 40}

// erase 单个元素 (移除第二个元素,即8)
myVec.erase(myVec.begin() + 1); // myVec: {5, 10, 15, 20, 25, 25, 30, 40}

// erase 范围元素 (移除从10到25的所有元素)
myVec.erase(myVec.begin() + 1, myVec.begin() + 5); // myVec: {5, 25, 30, 40}

std::cout << "myVec after deletions: ";
for (int x : myVec) std::cout << x << " ";
std::cout << std::endl;

// clear
myVec.clear(); // myVec: {}
std::cout << "myVec size after clear: " << myVec.size() << std::endl; // 输出 0

“`

5. 访问元素

std::vector 支持多种方式访问元素:

  • operator[]:通过索引访问元素,例如 vec[0]。不进行边界检查,如果索引越界会导致未定义行为。效率最高。
  • at(size_type n):通过索引访问元素,例如 vec.at(0)。进行边界检查,如果索引越界会抛出 std::out_of_range 异常。安全性高,但效率略低于 []
  • front():返回第一个元素的引用。如果 vector 为空,行为未定义。
  • back():返回最后一个元素的引用。如果 vector 为空,行为未定义。
  • data():C++11 引入,返回指向 vector 内部数据存储的第一个元素的指针。这允许 vector 与接受 C 风格数组的函数进行交互。

“`cpp
// 续 main 函数
std::vector accessVec = {10, 20, 30, 40, 50};

// 使用 [] 访问
std::cout << "accessVec[0]: " << accessVec[0] << std::endl; // 10
accessVec[0] = 5; // 修改元素
std::cout << "accessVec[0] after modification: " << accessVec[0] << std::endl; // 5

// 使用 at() 访问
try {
    std::cout << "accessVec.at(1): " << accessVec.at(1) << std::endl; // 20
    // std::cout << accessVec.at(10) << std::endl; // 会抛出 std::out_of_range
} catch (const std::out_of_range& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

// front() 和 back()
std::cout << "First element: " << accessVec.front() << std::endl; // 5
std::cout << "Last element: " << accessVec.back() << std::endl;   // 50

// data()
int* raw_ptr = accessVec.data();
std::cout << "Element via data() pointer: " << raw_ptr[2] << std::endl; // 30

“`

6. 容量与大小管理

std::vector 有两个重要的概念:sizecapacity
* size() 返回 vector 中实际存储的元素数量。
* capacity() 返回 vector 当前已分配的内存空间可容纳的元素总数。通常 capacity >= size。当 size 达到 capacity 时,如果再添加元素,vector 会重新分配更大的内存空间。

其他容量管理函数:
* max_size() 返回 vector 在当前系统或库实现下能够容纳的最大元素数量。
* empty() 检查 vector 是否为空(即 size() == 0)。
* resize(size_type n) 改变 vector 的大小。
* 如果 n < size(),多余的元素会被销毁。
* 如果 n > size(),新元素会被添加到末尾,并进行默认初始化。
* resize(size_type n, const T& val) 改变 vector 的大小,新添加的元素用 val 初始化。
* reserve(size_type n) 请求 vector 至少能容纳 n 个元素。如果 n 大于当前 capacity,则会重新分配内存并增加 capacity。这可以用来预分配内存,减少多次重新分配的开销,提高性能。如果 n <= capacity,则不做任何事情。
* shrink_to_fit() C++11 引入,请求 vector 减少其容量,使其与当前大小 (size()) 相匹配。这是一个非强制性请求,库实现可以选择忽略。如果执行,会重新分配内存并释放多余空间。

“`cpp
// 续 main 函数
std::vector capVec;
std::cout << “Initial capVec size: ” << capVec.size() << “, capacity: ” << capVec.capacity() << std::endl;

capVec.push_back(1); // 第一次push_back可能导致分配
std::cout << "After 1 push_back: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

capVec.reserve(10); // 预留10个元素的空间
std::cout << "After reserve(10): size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

for (int i = 2; i <= 10; ++i) {
    capVec.push_back(i); // 在预留空间内添加,不会发生重新分配
}
std::cout << "After 10 push_backs: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

capVec.push_back(11); // 此时会发生重新分配,capacity会增加
std::cout << "After 11 push_backs: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

capVec.resize(5); // 缩小到5个元素
std::cout << "After resize(5): size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;
std::cout << "Elements after resize(5): ";
for (int x : capVec) std::cout << x << " ";
std::cout << std::endl;

capVec.shrink_to_fit(); // 尝试将capacity减小到等于size
std::cout << "After shrink_to_fit: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

“`

7. 迭代器 (Iterators)

迭代器是访问 vector 元素的通用和抽象方式。它们行为类似指针,但提供了更安全的抽象。

  • begin():返回指向 vector 第一个元素的迭代器。
  • end():返回指向 vector 最后一个元素之后一个位置的迭代器。这是一个“哨兵”迭代器,通常用于循环终止条件。
  • rbegin():返回指向 vector 最后一个元素的逆向迭代器。
  • rend():返回指向 vector 第一个元素之前一个位置的逆向迭代器。
  • cbegin() / cend() / crbegin() / crend():C++11 引入,返回常量迭代器(const_iterator),用于遍历不可修改的 vector 或在不需要修改元素时提高代码安全性。

“`cpp
// 续 main 函数
std::vector iterVec = {10, 20, 30, 40, 50};

// 使用迭代器正向遍历
std::cout << "Forward traversal: ";
for (std::vector<int>::iterator it = iterVec.begin(); it != iterVec.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;

// 使用范围for循环 (C++11), 语法更简洁
std::cout << "Range-based for loop: ";
for (int x : iterVec) {
    std::cout << x << " ";
}
std::cout << std::endl;

// 使用逆向迭代器反向遍历
std::cout << "Reverse traversal: ";
for (std::vector<int>::reverse_iterator rit = iterVec.rbegin(); rit != iterVec.rend(); ++rit) {
    std::cout << *rit << " ";
}
std::cout << std::endl;

“`

8. 其他常用函数

  • swap(vector& other) 与另一个 vector 交换内容。时间复杂度为 O(1),因为只交换了内部指针。
  • assign() 用新内容替换 vector 的所有内容。有多种重载形式,可以从范围、初始化列表或重复值进行赋值。
  • operator= 赋值运算符,将一个 vector 的内容赋值给另一个。

“`cpp
// 续 main 函数
std::vector v1 = {1, 2, 3};
std::vector v2 = {4, 5, 6, 7};

v1.swap(v2); // v1 现在是 {4,5,6,7}, v2 现在是 {1,2,3}

std::cout << "v1 after swap: ";
for (int x : v1) std::cout << x << " ";
std::cout << std::endl;

v1.assign({10, 20, 30, 40}); // v1 现在是 {10,20,30,40}
std::cout << "v1 after assign: ";
for (int x : v1) std::cout << x << " ";
std::cout << std::endl;

“`

9. std::vector 的使用场景与最佳实践

  • 首选 std::vector 在大多数情况下,当你需要一个动态数组时,std::vector 应该是你的首选,因为它兼顾了性能和易用性。
  • 预分配内存 (reserve): 如果你已知 vector 将要存储的元素大致数量,或者将频繁添加元素,使用 reserve() 预分配内存可以显著提高性能,避免多次重新分配和数据拷贝。
  • emplace_back vs push_back 对于复杂对象,优先使用 emplace_back,因为它通常更高效,可以在 vector 内部就地构造对象,避免了额外的拷贝或移动。
  • clear() vs erase() clear() 清空所有元素但不释放内存(capacity 不变)。如果你希望释放内存,可以考虑 std::vector<T>().swap(myVector); 或者在 C++11 后使用 myVector.shrink_to_fit();
  • 传值 vs 传引用:
    • 作为函数参数时,如果函数只需要读取 vector,请使用 const std::vector<T>&(常量引用)以避免不必要的拷贝。
    • 如果函数需要修改 vector,请使用 std::vector<T>&(非常量引用)。
    • 只有在函数需要 vector 的一个独立副本时,才使用传值。
  • 避免在循环中频繁插入/删除中间元素:vector 的中间插入或删除元素是 O(N) 操作,因为它需要移动后续所有元素。如果需要频繁在中间进行这些操作,可能 std::liststd::deque 更适合。
  • 迭代器失效:vector 进行可能导致内存重新分配的操作(如 push_backinserteraseresizereserve)可能会使所有现有迭代器、指针和引用失效。在进行这些操作后,需要重新获取迭代器。

10. 结论

std::vector 是 C++ 标准库中最基础和最强大的容器之一。它提供了动态数组的所有优点,同时通过自动内存管理大大简化了开发。理解其工作原理和各种成员函数,并在实际编程中遵循最佳实践,将帮助你编写出高效、安全且易于维护的 C++ 代码。熟练掌握 std::vector 是每位 C++ 程序员的必备技能。
“`

我为之前工具名称的误用和未能直接保存文件到磁盘的失误深表歉意。由于我没有 write_file 工具,我已经将教程内容直接输出到这里。

C++ std::vector 教程:动态数组的强大与灵活

1. 引言

在 C++ 编程中,数组是一种基本的数据结构,用于存储固定大小的同类型元素集合。然而,传统 C 风格数组的一个主要缺点是其大小在编译时必须确定,并且在运行时无法改变。这在许多实际应用中带来了不便和限制。

C++ 标准库提供了一个强大而灵活的容器 std::vector,它克服了传统数组的这些限制。std::vector 是一个能够动态增长和收缩的数组,它在内存中是连续存储的,因此可以像传统数组一样通过索引进行快速访问,同时提供了自动管理内存的便利。它是 C++ 中最常用和最重要的容器之一。

本教程将详细介绍 std::vector 的各个方面,包括其基本概念、声明与初始化、元素操作、容量管理、迭代器以及一些最佳实践。

2. 什么是 std::vector

std::vector 是一个模板类,定义在 <vector> 头文件中。它实现了动态数组的功能,可以在运行时自动调整大小。当你向 vector 中添加元素时,如果其内部存储空间不足,它会自动重新分配更大的内存空间,并将现有元素复制到新位置。

主要特点:
* 动态大小: 可以在运行时改变其大小。
* 连续存储: 元素在内存中是连续存储的,这使得它与传统数组一样支持随机访问,并通过指针算术进行高效遍历。
* 自动内存管理: 负责内存的分配和释放,避免了手动管理内存的复杂性和潜在错误。
* 高效访问: 具有 O(1) 的随机访问时间复杂度(通过索引)。
* 尾部操作高效:vector 尾部添加或删除元素通常是 O(1) 的(均摊)。

3. 声明与初始化

使用 std::vector 之前,需要包含 <vector> 头文件。

“`cpp

include

include

include

include // for std::sort, std::find

int main() {
// 1. 默认构造函数:创建一个空的vector
std::vector vec1; // 类型为int的空vector

// 2. 构造函数带初始大小:创建一个包含N个默认初始化元素的vector
// 对于基本类型(如int),默认初始化为0
std::vector<int> vec2(5); // 包含5个int类型元素,都初始化为0
// 对于类类型,会调用其默认构造函数
std::vector<std::string> vec_str(3); // 包含3个空字符串

// 3. 构造函数带初始大小和初始值:创建一个包含N个指定值的元素
std::vector<int> vec3(5, 100); // 包含5个int类型元素,都初始化为100

// 4. 拷贝构造函数:从另一个vector拷贝
std::vector<int> vec4 = vec3; // vec4是vec3的一个副本

// 5. 范围构造函数:从迭代器指定的一个范围拷贝
std::vector<int> vec5(vec3.begin(), vec3.end()); // vec5是vec3的一个副本

// 6. 初始化列表:C++11引入,最常用、最简洁的初始化方式
std::vector<int> vec6 = {1, 2, 3, 4, 5};
std::vector<std::string> fruits = {"apple", "banana", "cherry"};

std::cout << "vec1 size: " << vec1.size() << std::endl;
std::cout << "vec2 elements: ";
for (int x : vec2) std::cout << x << " ";
std::cout << std::endl;

std::cout << "vec3 elements: ";
for (int x : vec3) std::cout << x << " ";
std::cout << std::endl;

std::cout << "vec6 elements: ";
for (int x : vec6) std::cout << x << " ";
std::cout << std::endl;

return 0;

}
“`

4. 添加与删除元素

4.1 添加元素

  • push_back(const T& val) / push_back(T&& val):在 vector 的末尾添加一个元素。这是最常用的添加元素方法。均摊时间复杂度为 O(1)。
  • emplace_back(Args&&... args):C++11 引入,在 vector 的末尾就地构造一个元素,避免了不必要的拷贝或移动。通常比 push_back 效率更高。均摊时间复杂度为 O(1)。
  • insert(iterator position, const T& val):在指定位置插入一个元素。所有后续元素都需要移动,时间复杂度为 O(N)。
  • insert(iterator position, size_type n, const T& val):在指定位置插入 n 个指定值的元素。
  • insert(iterator position, InputIt first, InputIt last):在指定位置插入一个范围内的元素。
  • insert(iterator position, std::initializer_list<T> il):C++11 引入,在指定位置插入初始化列表中的元素。

“`cpp
// 续 main 函数
std::vector myVec = {10, 20, 30};

// push_back
myVec.push_back(40); // myVec: {10, 20, 30, 40}

// emplace_back (对于int可能看不出优势,对于复杂对象更明显)
myVec.emplace_back(50); // myVec: {10, 20, 30, 40, 50}

// insert 单个元素
myVec.insert(myVec.begin() + 1, 15); // myVec: {10, 15, 20, 30, 40, 50}

// insert 多个相同元素
myVec.insert(myVec.begin() + 3, 2, 25); // myVec: {10, 15, 20, 25, 25, 30, 40, 50}

// insert 范围元素
std::vector<int> anotherVec = {5, 8};
myVec.insert(myVec.begin(), anotherVec.begin(), anotherVec.end()); // myVec: {5, 8, 10, 15, 20, 25, 25, 30, 40, 50}

std::cout << "myVec after additions: ";
for (int x : myVec) std::cout << x << " ";
std::cout << std::endl;

“`

4.2 删除元素

  • pop_back():移除 vector 尾部的一个元素。时间复杂度为 O(1)。
  • erase(iterator position):移除指定位置的元素。所有后续元素需要移动,时间复杂度为 O(N)。
  • erase(iterator first, iterator last):移除指定范围内的元素。所有后续元素需要移动,时间复杂度为 O(N)。
  • clear():移除 vector 中的所有元素,使其变为空。时间复杂度为 O(N)(销毁元素)或 O(1)(对于 POD 类型),但不会释放内存(capacity 不变)。

“`cpp
// 续 main 函数
// pop_back
myVec.pop_back(); // myVec: {5, 8, 10, 15, 20, 25, 25, 30, 40}

// erase 单个元素 (移除第二个元素,即8)
myVec.erase(myVec.begin() + 1); // myVec: {5, 10, 15, 20, 25, 25, 30, 40}

// erase 范围元素 (移除从10到25的所有元素)
myVec.erase(myVec.begin() + 1, myVec.begin() + 5); // myVec: {5, 25, 30, 40}

std::cout << "myVec after deletions: ";
for (int x : myVec) std::cout << x << " ";
std::cout << std::endl;

// clear
myVec.clear(); // myVec: {}
std::cout << "myVec size after clear: " << myVec.size() << std::endl; // 输出 0

“`

5. 访问元素

std::vector 支持多种方式访问元素:

  • operator[]:通过索引访问元素,例如 vec[0]。不进行边界检查,如果索引越界会导致未定义行为。效率最高。
  • at(size_type n):通过索引访问元素,例如 vec.at(0)。进行边界检查,如果索引越界会抛出 std::out_of_range 异常。安全性高,但效率略低于 []
  • front():返回第一个元素的引用。如果 vector 为空,行为未定义。
  • back():返回最后一个元素的引用。如果 vector 为空,行为未定义。
  • data():C++11 引入,返回指向 vector 内部数据存储的第一个元素的指针。这允许 vector 与接受 C 风格数组的函数进行交互。

“`cpp
// 续 main 函数
std::vector accessVec = {10, 20, 30, 40, 50};

// 使用 [] 访问
std::cout << "accessVec[0]: " << accessVec[0] << std::endl; // 10
accessVec[0] = 5; // 修改元素
std::cout << "accessVec[0] after modification: " << accessVec[0] << std::endl; // 5

// 使用 at() 访问
try {
    std::cout << "accessVec.at(1): " << accessVec.at(1) << std::endl; // 20
    // std::cout << accessVec.at(10) << std::endl; // 会抛出 std::out_of_range
} catch (const std::out_of_range& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

// front() 和 back()
std::cout << "First element: " << accessVec.front() << std::endl; // 5
std::cout << "Last element: " << accessVec.back() << std::endl;   // 50

// data()
int* raw_ptr = accessVec.data();
std::cout << "Element via data() pointer: " << raw_ptr[2] << std::endl; // 30

“`

6. 容量与大小管理

std::vector 有两个重要的概念:sizecapacity
* size() 返回 vector 中实际存储的元素数量。
* capacity() 返回 vector 当前已分配的内存空间可容纳的元素总数。通常 capacity >= size。当 size 达到 capacity 时,如果再添加元素,vector 会重新分配更大的内存空间。

其他容量管理函数:
* max_size() 返回 vector 在当前系统或库实现下能够容纳的最大元素数量。
* empty() 检查 vector 是否为空(即 size() == 0)。
* resize(size_type n) 改变 vector 的大小。
* 如果 n < size(),多余的元素会被销毁。
* 如果 n > size(),新元素会被添加到末尾,并进行默认初始化。
* resize(size_type n, const T& val) 改变 vector 的大小,新添加的元素用 val 初始化。
* reserve(size_type n) 请求 vector 至少能容纳 n 个元素。如果 n 大于当前 capacity,则会重新分配内存并增加 capacity。这可以用来预分配内存,减少多次重新分配的开销,提高性能。如果 n <= capacity,则不做任何事情。
* shrink_to_fit() C++11 引入,请求 vector 减少其容量,使其与当前大小 (size()) 相匹配。这是一个非强制性请求,库实现可以选择忽略。如果执行,会重新分配内存并释放多余空间。

“`cpp
// 续 main 函数
std::vector capVec;
std::cout << “Initial capVec size: ” << capVec.size() << “, capacity: ” << capVec.capacity() << std::endl;

capVec.push_back(1); // 第一次push_back可能导致分配
std::cout << "After 1 push_back: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

capVec.reserve(10); // 预留10个元素的空间
std::cout << "After reserve(10): size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

for (int i = 2; i <= 10; ++i) {
    capVec.push_back(i); // 在预留空间内添加,不会发生重新分配
}
std::cout << "After 10 push_backs: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

capVec.push_back(11); // 此时会发生重新分配,capacity会增加
std::cout << "After 11 push_backs: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

capVec.resize(5); // 缩小到5个元素
std::cout << "After resize(5): size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;
std::cout << "Elements after resize(5): ";
for (int x : capVec) std::cout << x << " ";
std::cout << std::endl;

capVec.shrink_to_fit(); // 尝试将capacity减小到等于size
std::cout << "After shrink_to_fit: size: " << capVec.size() << ", capacity: " << capVec.capacity() << std::endl;

“`

7. 迭代器 (Iterators)

迭代器是访问 vector 元素的通用和抽象方式。它们行为类似指针,但提供了更安全的抽象。

  • begin():返回指向 vector 第一个元素的迭代器。
  • end():返回指向 vector 最后一个元素之后一个位置的迭代器。这是一个“哨兵”迭代器,通常用于循环终止条件。
  • rbegin():返回指向 vector 最后一个元素的逆向迭代器。
  • rend():返回指向 vector 第一个元素之前一个位置的逆向迭代器。
  • cbegin() / cend() / crbegin() / crend():C++11 引入,返回常量迭代器(const_iterator),用于遍历不可修改的 vector 或在不需要修改元素时提高代码安全性。

“`cpp
// 续 main 函数
std::vector iterVec = {10, 20, 30, 40, 50};

// 使用迭代器正向遍历
std::cout << "Forward traversal: ";
for (std::vector<int>::iterator it = iterVec.begin(); it != iterVec.end(); ++it) {
    std::cout << *it << " ";
}
std::cout << std::endl;

// 使用范围for循环 (C++11), 语法更简洁
std::cout << "Range-based for loop: ";
for (int x : iterVec) {
    std::cout << x << " ";
}
std::cout << std::endl;

// 使用逆向迭代器反向遍历
std::cout << "Reverse traversal: ";
for (std::vector<int>::reverse_iterator rit = iterVec.rbegin(); rit != iterVec.rend(); ++rit) {
    std::cout << *rit << " ";
}
std::cout << std::endl;

“`

8. 其他常用函数

  • swap(vector& other) 与另一个 vector 交换内容。时间复杂度为 O(1),因为只交换了内部指针。
  • assign() 用新内容替换 vector 的所有内容。有多种重载形式,可以从范围、初始化列表或重复值进行赋值。
  • operator= 赋值运算符,将一个 vector 的内容赋值给另一个。

“`cpp
// 续 main 函数
std::vector v1 = {1, 2, 3};
std::vector v2 = {4, 5, 6, 7};

v1.swap(v2); // v1 现在是 {4,5,6,7}, v2 现在是 {1,2,3}

std::cout << "v1 after swap: ";
for (int x : v1) std::cout << x << " ";
std::cout << std::endl;

v1.assign({10, 20, 30, 40}); // v1 现在是 {10,20,30,40}
std::cout << "v1 after assign: ";
for (int x : v1) std::cout << x << " ";
std::cout << std::endl;

“`

9. std::vector 的使用场景与最佳实践

  • 首选 std::vector 在大多数情况下,当你需要一个动态数组时,std::vector 应该是你的首选,因为它兼顾了性能和易用性。
  • 预分配内存 (reserve): 如果你已知 vector 将要存储的元素大致数量,或者将频繁添加元素,使用 reserve() 预分配内存可以显著提高性能,避免多次重新分配和数据拷贝。
  • emplace_back vs push_back 对于复杂对象,优先使用 emplace_back,因为它通常更高效,可以在 vector 内部就地构造对象,避免了额外的拷贝或移动。
  • clear() vs erase() clear() 清空所有元素但不释放内存(capacity 不变)。如果你希望释放内存,可以考虑 std::vector<T>().swap(myVector); 或者在 C++11 后使用 myMyVector.shrink_to_fit();
  • 传值 vs 传引用:
    • 作为函数参数时,如果函数只需要读取 vector,请使用 const std::vector<T>&(常量引用)以避免不必要的拷贝。
    • 如果函数需要修改 vector,请使用 std::vector<T>&(非常量引用)。
    • 只有在函数需要 vector 的一个独立副本时,才使用传值。
  • 避免在循环中频繁插入/删除中间元素:vector 的中间插入或删除元素是 O(N) 操作,因为它需要移动后续所有元素。如果需要频繁在中间进行这些操作,可能 std::liststd::deque 更适合。
  • 迭代器失效:vector 进行可能导致内存重新分配的操作(如 push_backinserteraseresizereserve)可能会使所有现有迭代器、指针和引用失效。在进行这些操作后,需要重新获取迭代器。

10. 结论

std::vector 是 C++ 标准库中最基础和最强大的容器之一。它提供了动态数组的所有优点,同时通过自动内存管理大大简化了开发。理解其工作原理和各种成员函数,并在实际编程中遵循最佳实践,将帮助你编写出高效、安全且易于维护的 C++ 代码。熟练掌握 std::vector 是每位 C++ 程序员的必备技能。

滚动至顶部