C++.vector 容器(1.5w字)
1. C++ vector 容器概述
1.1 定义与特点
std::vector
是 C++ 标准模板库(STL)中的一个序列容器,它是一个封装了动态大小数组的序列容器。std::vector
的数据存储在连续的内存空间中,这使得它能够高效地进行随机访问,但插入和删除操作(特别是非尾部操作)可能会比较慢,因为可能需要移动元素以保持内存的连续性。
- 动态扩容:
std::vector
的一个显著特点是动态扩容。当容器中的元素数量超过当前分配的内存容量时,std::vector
会自动分配更大的内存空间,并将原有元素复制到新的内存中。通常,新的容量是当前容量的两倍,但具体扩容策略可能因实现而异。 - 随机访问:由于
std::vector
的元素存储在连续的内存中,因此它支持高效的随机访问。通过索引可以直接访问任意位置的元素,时间复杂度为 O(1)。 - 内存连续性:
std::vector
的元素存储在连续的内存块中,这使得它在某些情况下(如与 C 风格的代码或某些硬件优化相关时)具有优势。然而,这也意味着插入或删除元素(特别是非尾部操作)可能会导致大量元素的移动,从而降低性能。 - 迭代器支持:
std::vector
提供了完整的迭代器支持,包括随机访问迭代器。这意味着可以使用标准的迭代器操作(如begin()
、end()
、++
、--
、*
等)来遍历容器中的元素。
以下是 std::vector
的基本定义和初始化示例:
#include <iostream>
#include <vector>int main() {// 默认构造函数,创建一个空的 vectorstd::vector<int> vec1;std::cout << "vec1 size: " << vec1.size() << ", capacity: " << vec1.capacity() << std::endl;// 使用初始化列表构造 vectorstd::vector<int> vec2 = {1, 2, 3, 4, 5};std::cout << "vec2 size: " << vec2.size() << ", capacity: " << vec2.capacity() << std::endl;// 使用指定大小和默认值构造 vectorstd::vector<int> vec3(10, 0); // 创建一个包含 10 个元素,每个元素值为 0 的 vectorstd::cout << "vec3 size: " << vec3.size() << ", capacity: " << vec3.capacity() << std::endl;// 使用另一个 vector 的范围构造新的 vectorstd::vector<int> vec4(vec2.begin(), vec2.end());std::cout << "vec4 size: " << vec4.size() << ", capacity: " << vec4.capacity() << std::endl;return 0;
}
1.2 使用场景
std::vector
是 C++ 中最常用的容器之一,适用于多种场景,尤其是在需要动态数组功能时。以下是一些常见的使用场景:
- 动态数组:当需要一个可以动态增长和收缩的数组时,
std::vector
是理想的选择。它可以自动管理内存,避免手动分配和释放内存的复杂性。 - 存储和管理数据集合:
std::vector
可以用来存储和管理一组同类型的数据,例如存储用户信息、记录日志、处理数据集等。 - 与算法配合:由于
std::vector
提供了随机访问迭代器,它与 STL 中的算法(如std::sort
、std::find
等)配合得非常好,可以高效地执行各种操作。 - 临时存储:在函数调用或算法实现中,
std::vector
可以用作临时存储容器,用于存储中间结果或临时数据。
以下是 std::vector
在实际场景中的应用示例:
#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::sortint main() {// 动态数组示例std::vector<int> vec;for (int i = 0; i < 10; ++i) {vec.push_back(i * i); // 动态添加元素}std::cout << "Dynamic array: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;// 与算法配合示例std::vector<int> data = {5, 2, 9, 1, 5, 6};std::sort(data.begin(), data.end()); // 使用 std::sort 对 vector 进行排序std::cout << "Sorted data: ";for (int num : data) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
在上述代码中,std::vector
用于动态存储平方数,并与 std::sort
算法配合对数据进行排序,展示了其在动态数组和算法配合方面的强大功能。
2. vector 容器的创建与初始化
2.1 默认创建空 vector
std::vector
的默认构造函数用于创建一个空的容器,此时容器的大小(size
)和容量(capacity
)均为 0。这种方式适用于需要在后续动态添加元素的场景。
#include <iostream>
#include <vector>int main() {// 默认创建一个空的 vectorstd::vector<int> vec;std::cout << "vec size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;return 0;
}
运行结果:
vec size: 0, capacity: 0
2.2 指定大小与初始值初始化
可以通过指定容器的大小和初始值来初始化 std::vector
。这种方式在需要预先分配一定数量的元素并设置默认值时非常有用。
#include <iostream>
#include <vector>int main() {// 指定大小和初始值初始化std::vector<int> vec(10, 42); // 创建一个包含 10 个元素,每个元素值为 42 的 vectorstd::cout << "vec size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果:
vec size: 10, capacity: 10
42 42 42 42 42 42 42 42 42 42
2.3 使用列表初始化
使用初始化列表({}
)可以方便地将一组值直接赋给 std::vector
。这种方式适用于已知一组初始值的情况。
#include <iostream>
#include <vector>int main() {// 使用初始化列表初始化std::vector<int> vec = {1, 2, 3, 4, 5};std::cout << "vec size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果:
vec size: 5, capacity: 5
1 2 3 4 5
2.4 拷贝初始化
通过拷贝构造函数可以创建一个与另一个 std::vector
相同内容的新容器。这种方式适用于需要复制现有容器数据的场景。
#include <iostream>
#include <vector>int main() {// 创建一个原始 vectorstd::vector<int> vec1 = {1, 2, 3, 4, 5};// 拷贝初始化std::vector<int> vec2(vec1);std::cout << "vec1 size: " << vec1.size() << ", capacity: " << vec1.capacity() << std::endl;std::cout << "vec2 size: " << vec2.size() << ", capacity: " << vec2.capacity() << std::endl;for (int num : vec2) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果:
vec1 size: 5, capacity: 5
vec2 size: 5, capacity: 5
1 2 3 4 5
2.5 使用指针或迭代器初始化
可以通过指针或迭代器指定一个范围来初始化 std::vector
。这种方式适用于从其他容器或数组中提取一部分数据来初始化。
#include <iostream>
#include <vector>int main() {// 创建一个原始数组int arr[] = {1, 2, 3, 4, 5};// 使用指针初始化std::vector<int> vec1(arr, arr + 3); // 从数组中提取前 3 个元素std::cout << "vec1 size: " << vec1.size() << ", capacity: " << vec1.capacity() << std::endl;for (int num : vec1) {std::cout << num << " ";}std::cout << std::endl;// 创建一个原始 vectorstd::vector<int> vec2 = {1, 2, 3, 4, 5};// 使用迭代器初始化std::vector<int> vec3(vec2.begin() + 1, vec2.end() - 1); // 从 vec2 中提取索引 1 到 3 的元素std::cout << "vec3 size: " << vec3.size() << ", capacity: " << vec3.capacity() << std::endl;for (int num : vec3) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果:
vec1 size: 3, capacity: 3
1 2 3
vec3 size: 3, capacity: 3
2 3 4
3. vector 容器的元素访问
3.1 使用下标操作符 []
std::vector
支持使用下标操作符 []
来访问和修改容器中的元素。这种方式类似于访问普通数组的元素,通过指定索引值来直接访问对应的元素。其时间复杂度为 O(1),因为 std::vector
的元素存储在连续的内存中,可以直接通过计算偏移量来访问元素。
示例代码
#include <iostream>
#include <vector>int main() {// 创建一个 vectorstd::vector<int> vec = {10, 20, 30, 40, 50};// 使用下标操作符访问元素std::cout << "Element at index 2: " << vec[2] << std::endl; // 输出 30// 修改元素vec[2] = 35;std::cout << "Modified element at index 2: " << vec[2] << std::endl; // 输出 35// 遍历 vector 并输出所有元素for (size_t i = 0; i < vec.size(); ++i) {std::cout << "Element at index " << i << ": " << vec[i] << std::endl;}return 0;
}
运行结果
Element at index 2: 30
Modified element at index 2: 35
Element at index 0: 10
Element at index 1: 20
Element at index 2: 35
Element at index 3: 40
Element at index 4: 50
注意事项
- 下标操作符
[]
不会进行边界检查。如果索引值超出容器的范围,可能会导致未定义行为,例如访问无效内存或程序崩溃。 - 在使用
[]
访问元素时,需要确保索引值在合法范围内,否则可能会引发运行时错误。
3.2 使用 at() 方法
std::vector
提供了 at()
方法来访问和修改容器中的元素。与下标操作符 []
不同,at()
方法会进行边界检查。如果索引值超出容器的范围,at()
方法会抛出 std::out_of_range
异常。这种方式在需要确保索引值合法时更为安全。
示例代码
#include <iostream>
#include <vector>
#include <stdexcept> // 用于 std::out_of_rangeint main() {// 创建一个 vectorstd::vector<int> vec = {10, 20, 30, 40, 50};try {// 使用 at() 方法访问元素std::cout << "Element at index 2: " << vec.at(2) << std::endl; // 输出 30// 修改元素vec.at(2) = 35;std::cout << "Modified element at index 2: " << vec.at(2) << std::endl; // 输出 35// 尝试访问超出范围的元素std::cout << "Element at index 10: " << vec.at(10) << std::endl;} catch (const std::out_of_range& e) {std::cerr << "Error: " << e.what() << std::endl; // 捕获并处理异常}// 遍历 vector 并输出所有元素for (size_t i = 0; i < vec.size(); ++i) {std::cout << "Element at index " << i << ": " << vec.at(i) << std::endl;}return 0;
}
运行结果
Element at index 2: 30
Modified element at index 2: 35
Error: vector::_M_range_check: __n (which is 10) >= this->size() (which is 5)
Element at index 0: 10
Element at index 1: 20
Element at index 2: 35
Element at index 3: 40
Element at index 4: 50
注意事项
at()
方法会进行边界检查,因此在访问元素时会比[]
操作符稍慢,但安全性更高。- 如果需要频繁访问大量元素且已确保索引值合法,建议使用
[]
操作符以提高性能。 - 在不确定索引值是否合法时,建议使用
at()
方法以避免潜在的运行时错误。 -
4. vector 容器的元素添加与删除
4.1 添加元素
4.1.1 使用 push_back() 添加元素到尾部
push_back()
是 std::vector
提供的一个非常常用的方法,用于在容器的尾部添加一个元素。当调用 push_back()
时,如果当前容器的容量不足以容纳新元素,std::vector
会自动进行扩容,通常是将容量扩大为原来的两倍(具体策略可能因实现而异)。这种方式的时间复杂度为 O(1),但在扩容时可能会涉及到元素的复制,因此在大量连续添加元素时,可能会有一定的性能开销。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec;// 使用 push_back() 添加元素vec.push_back(10);vec.push_back(20);vec.push_back(30);std::cout << "Vector elements: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;return 0;
}
运行结果
Vector elements: 10 20 30
Size: 3, Capacity: 4
注意事项
push_back()
只能在容器的尾部添加元素,如果需要在其他位置添加元素,应使用insert()
方法。- 在连续添加大量元素时,为了避免频繁的扩容操作,可以预先使用
reserve()
方法分配足够的容量,从而提高性能。
4.1.2 使用 insert() 在指定位置插入元素
insert()
方法允许在 std::vector
的任意指定位置插入一个或多个元素。它需要一个迭代器作为插入位置的指示,以及要插入的元素或元素范围。插入操作可能会导致容器中的元素移动,以保持内存的连续性,因此其时间复杂度取决于插入位置和插入元素的数量。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 在指定位置插入单个元素auto it = vec.begin() + 2; // 指向索引为 2 的位置vec.insert(it, 25); // 在索引 2 的位置插入 25std::cout << "Vector after inserting single element: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;// 在指定位置插入多个元素it = vec.begin() + 4; // 指向索引为 4 的位置vec.insert(it, {35, 36, 37}); // 在索引 4 的位置插入 35, 36, 37std::cout << "Vector after inserting multiple elements: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果
Vector after inserting single element: 10 20 25 30 40 50
Vector after inserting multiple elements: 10 20 25 30 35 36 37 40 50
注意事项
- 插入操作会导致插入点之后的所有元素向后移动,因此可能会有一定的性能开销,尤其是在容器较大且插入位置较靠前时。
- 如果插入的元素数量较多,建议预先使用
reserve()
方法分配足够的容量,以减少扩容操作的次数。
4.2 删除元素
4.2.1 使用 pop_back() 删除尾部元素
pop_back()
方法用于删除 std::vector
中的最后一个元素。它是一个非常高效的操作,时间复杂度为 O(1),因为它不需要移动任何元素。调用 pop_back()
后,容器的大小会减少 1,但容量不会立即改变。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 使用 pop_back() 删除尾部元素vec.pop_back();std::cout << "Vector after pop_back(): ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;return 0;
}
运行结果
Vector after pop_back(): 10 20 30 40
Size: 4, Capacity: 8
注意事项
pop_back()
只能删除容器的最后一个元素,如果需要删除其他位置的元素,应使用erase()
方法。- 如果容器为空时调用
pop_back()
,会导致未定义行为,因此在使用前应确保容器不为空。
4.2.2 使用 erase() 删除指定位置元素
erase()
方法用于删除 std::vector
中指定位置的一个或多个元素。它需要一个迭代器或一个迭代器范围作为参数,指定要删除的元素位置。删除操作会导致被删除元素之后的所有元素向前移动,因此其时间复杂度取决于删除位置和删除元素的数量。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 删除指定位置的单个元素auto it = vec.begin() + 2; // 指向索引为 2 的位置vec.erase(it); // 删除索引为 2 的元素std::cout << "Vector after erasing single element: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;// 删除指定范围的多个元素it = vec.begin() + 1; // 指向索引为 1 的位置vec.erase(it, it + 2); // 删除索引为 1 到 2 的元素std::cout << "Vector after erasing multiple elements: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果
Vector after erasing single element: 10 20 40 50
Vector after erasing multiple elements: 10 50
注意事项
- 删除操作会导致被删除元素之后的所有元素向前移动,因此可能会有一定的性能开销,尤其是在容器较大且删除位置较靠前时。
- 如果需要删除多个元素,建议使用迭代器范围作为参数,以提高代码的可读性和效率。
- 在删除元素后,迭代器可能会失效,因此在使用迭代器时需要注意迭代器的有效性。
5. vector 容器的迭代器使用
5.1 获取迭代器
5.1.1 使用 begin() 获取首元素迭代器
begin()
方法用于获取指向 std::vector
容器中第一个元素的迭代器。通过这个迭代器,可以访问容器中的第一个元素,并且可以用于遍历容器。begin()
方法返回的是一个随机访问迭代器,支持所有标准迭代器操作。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 获取首元素迭代器std::vector<int>::iterator it = vec.begin();// 通过迭代器访问首元素std::cout << "First element: " << *it << std::endl;return 0;
}
运行结果
First element: 10
5.1.2 使用 end() 获取末尾迭代器
end()
方法用于获取指向 std::vector
容器中最后一个元素之后一个位置的迭代器。这个迭代器本身不指向任何有效的元素,但可以用于比较和遍历容器的末尾。end()
方法返回的也是随机访问迭代器。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 获取末尾迭代器std::vector<int>::iterator it = vec.end();// 末尾迭代器不能直接解引用,但可以用于比较std::cout << "End iterator points to: " << (it - vec.begin()) << " (beyond last element)" << std::endl;return 0;
}
运行结果
End iterator points to: 5 (beyond last element)
5.2 迭代器遍历
5.2.1 使用迭代器遍历 vector 容器
通过迭代器可以高效地遍历 std::vector
容器中的所有元素。这种方式不仅灵活,而且可以利用迭代器的特性(如随机访问)来实现更复杂的操作。遍历时,通常从 begin()
开始,直到 end()
结束。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 使用迭代器遍历 vector 容器std::cout << "Vector elements: ";for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用范围 for 循环(基于迭代器的简化写法)std::cout << "Vector elements (range-based for loop): ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果
Vector elements: 10 20 30 40 50
Vector elements (range-based for loop): 10 20 30 40 50
注意事项
- 迭代器在容器被修改(如插入或删除元素)后可能会失效,因此在修改容器时需要重新获取迭代器。
- 使用迭代器遍历时,确保迭代器的范围正确,避免访问无效内存。
- 范围 for 循环是 C++11 引入的语法糖,底层仍然是基于迭代器实现的,但使用起来更加简洁。
6. vector 容器的容量与大小管理
6.1 查询大小
6.1.1 使用 size() 查询元素数量
size()
方法用于查询 std::vector
容器中当前存储的元素数量。该方法返回一个无符号整数,表示容器中实际存在的元素个数。其时间复杂度为 O(1),因为它直接返回容器内部维护的大小信息。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 查询元素数量std::cout << "Number of elements in vector: " << vec.size() << std::endl;return 0;
}
运行结果
Number of elements in vector: 5
6.1.2 使用 capacity() 查询当前容量
capacity()
方法用于查询 std::vector
容器当前分配的内存容量,即容器能够容纳的元素数量,而不需要进行扩容操作。该方法返回一个无符号整数,表示容器当前的容量。其时间复杂度为 O(1)。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 查询当前容量std::cout << "Current capacity of vector: " << vec.capacity() << std::endl;return 0;
}
运行结果
Current capacity of vector: 8
6.1.3 使用 max_size() 查询最大允许容量
max_size()
方法用于查询 std::vector
容器能够支持的最大元素数量。这个值通常受到系统内存和实现限制。该方法返回一个无符号整数,表示容器的最大容量。其时间复杂度为 O(1)。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec;// 查询最大允许容量std::cout << "Maximum allowed size of vector: " << vec.max_size() << std::endl;return 0;
}
运行结果
Maximum allowed size of vector: 9223372036854775807
6.2 修改大小
6.2.1 使用 resize() 修改大小
resize()
方法用于修改 std::vector
容器的大小。如果新大小大于当前大小,容器会自动添加默认值(或指定值)的元素;如果新大小小于当前大小,容器会删除多余的元素。该方法的时间复杂度取决于新大小与当前大小的差值。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 扩大容器大小vec.resize(8, 60); // 添加 3 个值为 60 的元素std::cout << "Vector after resizing to larger size: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;// 缩小容器大小vec.resize(3); // 删除多余的元素std::cout << "Vector after resizing to smaller size: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果
Vector after resizing to larger size: 10 20 30 40 50 60 60 60
Vector after resizing to smaller size: 10 20 30
6.2.2 使用 reserve() 预分配空间
reserve()
方法用于预分配 std::vector
容器的内存空间,以提高性能。该方法不会改变容器的大小(size
),但会改变容器的容量(capacity
)。预分配空间可以减少在连续添加元素时的扩容操作次数。其时间复杂度为 O(1),但如果需要分配更多内存,则可能涉及内存分配操作。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec;// 预分配空间vec.reserve(10); // 预分配 10 个元素的空间// 添加元素for (int i = 0; i < 10; ++i) {vec.push_back(i * i);}std::cout << "Vector elements: ";for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;return 0;
}
运行结果
Vector elements: 0 1 4 9 16 25 36 49 64 81
Size: 10, Capacity: 10
7. vector 容器的其他操作
7.1 判断容器是否为空
7.1.1 使用 empty() 判断
empty()
方法用于判断 std::vector
容器是否为空。如果容器中没有任何元素,则返回 true
;否则返回 false
。该方法的时间复杂度为 O(1),因为它直接检查容器的大小信息。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec1;std::vector<int> vec2 = {10, 20, 30};// 判断容器是否为空if (vec1.empty()) {std::cout << "vec1 is empty." << std::endl;} else {std::cout << "vec1 is not empty." << std::endl;}if (vec2.empty()) {std::cout << "vec2 is empty." << std::endl;} else {std::cout << "vec2 is not empty." << std::endl;}return 0;
}
运行结果
vec1 is empty.
vec2 is not empty.
7.2 清空容器
7.2.1 使用 clear() 清空元素
clear()
方法用于清空 std::vector
容器中的所有元素。调用 clear()
后,容器的大小将变为 0,但容量不会改变。这意味着容器仍然保留了之前分配的内存空间,但所有元素都被移除。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 清空容器vec.clear();std::cout << "Vector size after clear(): " << vec.size() << std::endl;std::cout << "Vector capacity after clear(): " << vec.capacity() << std::endl;return 0;
}
运行结果
Vector size after clear(): 0
Vector capacity after clear(): 8
7.3 元素交换
7.3.1 使用 swap() 交换两个容器数据
swap()
方法用于交换两个 std::vector
容器中的数据。调用 swap()
后,两个容器的内容将互换,但它们的容量不会改变。这种方式的时间复杂度为 O(1),因为它只是交换了两个容器的内部指针,而不是复制元素。
示例代码
#include <iostream>
#include <vector>int main() {std::vector<int> vec1 = {10, 20, 30};std::vector<int> vec2 = {40, 50, 60};// 交换两个容器的数据vec1.swap(vec2);std::cout << "vec1 after swap: ";for (int num : vec1) {std::cout << num << " ";}std::cout << std::endl;std::cout << "vec2 after swap: ";for (int num : vec2) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
运行结果
vec1 after swap: 40 50 60
vec2 after swap: 10 20 30