【C++ STL】vector容器
概述
std::vector 是 STL 提供的内存连续的、可变长度的动态数组容器,可以灵活地管理元素,并且具有许多重要的特性。
- vector是一个动态数组容器,可以自动调整大小以适应存储元素的需求。它在内部使用连续的内存来存储元素。
- vector允许在尾部高效地添加和删除元素,通过使用
push_back和pop_back操作。O(N) - vector支持随机访问元素,可像普通数组一样使用索引访问数据。使用[]运算符或at()函数可以获得指定位置的元素。O(1)
- vector是一个模板类,可以存储任意类型的对象,包括内置类型和自定义类。
- vector会自动处理内存分配和释放,不需要手动管理内存。
- vector的迭代器用于遍历容器中的元素,可以进行正向和反向遍历。
- vector可以通过拷贝构造函数和赋值运算符进行复制和赋值,使得可以方便地创建副本或对容器进行复制。
- vector的缺点:在尾部添加或删除元素时效率较高,但在中间或开头位置插入或删除元素会导致数据的移动,影响性能。

构造函数
- 默认构造函数:创建一个空的vector对象。
vector(); - 带初始大小和值的构造函数:创建一个包含
count个元素的vector对象,每个元素都被初始化为value的副本。vector(size_type count, const T& value = T()); - 带初始范围的构造函数:创建一个包含范围在
[first, last)内的元素的vector对象。可以使用迭代器、指针或者数组来指定范围。template <class InputIterator> vector(InputIterator first, InputIterator last); - 拷贝构造函数:根据另一个
vector对象other创建一个新的vector对象,包含与other相同的元素。vector(const vector& other); - 移动构造函数:根据另一个可移动的
vector对象other创建一个新的vector对象,other中的元素会被移动到新的vector对象中,原vector对象将处于有效但未指定状态。vector(vector&& other); - 初始化列表构造函数:使用初始化列表初始化一个vector对象,列表中的元素被复制到vector中。
vector(std::initializer_list<T> init);
示例:
// 1. 创建空vector; 常数复杂度
vector<int> v0;
// 这句代码可以使得向vector中插入前3个元素时,保证常数时间复杂度
v0.reserve(3);
// 2. 创建一个初始空间为3的vector,其元素的默认值是0; 线性复杂度
vector<int> v1(3);
// 3. 创建一个初始空间为3的vector,其元素的默认值是2; 线性复杂度
vector<int> v2(3, 2);
// 4. 创建一个初始空间为3的vector,其元素的默认值是1,
// 并且使用v2的空间配置器; 线性复杂度
vector<int> v3(3, 1, v2.get_allocator());
// 5. 创建一个v2的拷贝vector v4, 其内容元素和v2一样; 线性复杂度
vector<int> v4(v2);
// 6. 创建一个v4的拷贝vector v5,其内容是{v4[1], v4[2]}; 线性复杂度
vector<int> v5(v4.begin() + 1, v4.begin() + 3);
// 7. 移动v2到新创建的vector v6,不发生拷贝; 常数复杂度; 需要 C++11
vector<int> v6(std::move(v2)); // 或者 v6 = std::move(v2);
赋值
-
使用等号赋值:将另一个vector的内容赋值给当前的vector。
vector<T>& operator=(const vector<T>& other);示例:
vector<int> v1; vector<int> v2 = {1, 2, 3}; v1 = v2; // 将v2中的元素赋值给v1 for (int num : v1) { cout << num << " "; } // 输出结果: 1 2 3 -
使用
assign()函数赋值:接受输入迭代器范围或元素数量与值,并用其进行赋值。void assign(InputIt first, InputIt last); void assign(size_type count, const T& value);示例:
vector<int> v1; vector<int> v2 = {4, 5, 6}; v1.assign(v2.begin(), v2.end()); // 将v2中指定范围内的元素赋值给v1 for (int num : v1) { cout << num << " "; } // 输出结果: 4 5 6 vector<int> v3; v3.assign(3, 100); // 将v3中的元素数量设置为3,并将每个元素的值设置为100 for (int num : v3) { cout << num << " "; } // 输出结果: 100 100 100 -
使用拷贝构造函数进行赋值:使用另一个vector创建一个新的vector,并将其元素拷贝到新的vector中。
vector(const vector& other);示例:
vector<int> v2 = {7, 8, 9}; vector<int> v1(v2); // 创建一个新的vector v1,并将v2中的元素拷贝给v1 for (int num : v1) { cout << num << " "; } // 输出结果: 7 8 9 -
使用初始化列表进行赋值:使用初始化列表创建一个新的vector,并将列表中的元素赋值给它。
vector(std::initializer_list<T> init);示例:
vector<int> v1 = {10, 11, 12}; // 使用初始化列表赋值给v1 for (int num : v1) { cout << num << " "; } // 输出结果: 10 11 12 -
二维初始化
vector<int> v[5];//定义可变长二维数组 //注意:行不可变(只有5行), 而列可变,可以在指定行添加元素 //第一维固定长度为5,第二维长度可以改变 //初始化二维均可变长数组 vector<vectot<int>> v;//定义一个行和列均可变的二维数组 //行列长度均固定 n + 1行 m + 1列初始值为0 vector<vector<int>> a(n + 1, vector<int>(m + 1, 0)); //c++17或者c++20支持的形式(不常用) vector a(n + 1, vector(m + 1, 0));
方法函数
| 方法 | 含义 |
|---|---|
| 长度/容量 | |
size() |
返回当前vector中元素的数量。 O(1) |
empty() |
检查当前vector是否为空。如果为空,返回true;否则返回false。 O(1) |
resize(n, v) |
改变数组大小为n,新增空间数值赋为v,如果没有默认赋值为0 |
max_size() |
返回容器的最大可能长度。 |
capacity() |
返回vector当前所分配的存储空间的容量(即可以容纳的元素数量),而不是实际存储的元素数量。一般情况下,容量会大于或等于size()函数返回的大小。 |
reserve(n) |
修改vector的容量,确保存储空间至少能容纳指定的元素数量n。如果n小于当前容量,则函数调用没有任何效果。如果n大于当前容量,则会重新分配足够的存储空间以适应新的容量。 |
shrink_to_fit() |
使得 vector 的容量与长度一致,多退但不会少。 |
| 访问元素 | |
at(pos) |
返回容器中下标为 pos 的引用。如果数组越界抛出 std::out_of_range 类型的异常。O(1) |
front() |
返回首元素的引用。O(1) |
back() |
返回末尾元素的引用。O(1) |
data() |
返回指向数组第一个元素的指针。 |
| 增删元素 | |
push_back(element) |
在尾部加一个数据O(1) |
emplace_back(element) |
在尾部构造一个数据O(1) (C++11) |
insert(it, x) |
向任意迭代器it插入一个元素x ,O(N) |
pop_back() |
删除最后一个数据O(1) |
erase(first,last) |
删除[first,last)的所有元素,O(N) |
clear() |
清除所有元素。仅清空了大小,容量还是存在的。 |
| 迭代器 | |
begin() / cbegin() |
返回指向首元素的迭代器(地址),其中 *begin = front |
end() / cend() |
返回指向数组尾端占位符的迭代器,注意是没有元素的。 |
rbegin() / crbegin() |
返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素。*rbegin = back |
rend() / crend() |
返回指向逆向数组末元素后一位置的迭代器,对应容器首的前一个位置,没有元素。 |
含有字符 c 的为只读迭代器,你不能通过只读迭代器去修改 vector 中的元素的值。如果一个 vector本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。
存取元素
-
at()访问//at()函数的原型: reference at(size_type pos); const_reference at(size_type pos) const;at()函数返回指定位置pos处的元素的引用。如果pos超出了有效范围(即大于等于size()),则抛出std::out_of_range异常。
std::vector<int> nums = {1, 2, 3, 4, 5}; for (size_t i = 0; i < nums.size(); ++i) { std::cout << "Element at position " << i << ": " << nums.at(i) << std::endl; } //Element at position 0: 1 //Element at position 1: 2 //Element at position 2: 3 //Element at position 3: 4 //Element at position 4: 5 -
下标访问
//operator[] 函数的原型: reference operator[](size_type pos); const_reference operator[](size_type pos) const;operator[]函数返回指定位置pos处的元素的引用。与at()函数不同的是,若pos超出有效范围,则行为是未定义的。也就是说at越界时会抛出异常,而[]越界不会抛出异常。
std::vector<int> nums = {1, 2, 3, 4, 5}; for (size_t i = 0; i < nums.size(); ++i) { std::cout << "Element at position " << i << ": " << nums[i] << std::endl; } //Element at position 0: 1 //Element at position 1: 2 //Element at position 2: 3 //Element at position 3: 4 //Element at position 4: 5 -
迭代器访问
std::vector<int> nums = {1, 2, 3, 4, 5}; std::vector<int>::iterator it; for (it = nums.begin(); it != nums.end(); it++) { std::cout << "Element at position " << i << ": " << *it << std::endl; } //Element at position 0: 1 //Element at position 1: 2 //Element at position 2: 3 //Element at position 3: 4 //Element at position 4: 5 -
data()访问//data()函数的原型: T* data(); const T* data() const;data()函数返回指向容器存储数据的指针。这个指针允许对容器中的元素进行修改。std::vector<int> nums = {1, 2, 3, 4, 5}; int* ptr = nums.data(); for (size_t i = 0; i < nums.size(); ++i) { std::cout << "Element at position " << i << ": " << *(ptr + i) << std::endl; } //Element at position 0: 1 //Element at position 1: 2 //Element at position 2: 3 -
C++11遍历
只能遍历完整数组,如果要指定的内容进行遍历,需要另选方法。
auto 能够自动识别并获取类型。vector<int> v; v.push_back(12); v.push_back(241); for(auto val : v) cout << val << " "; // 12 241
增删元素
-
push_back()/emplace_back():将元素添加到vector的末尾。//原型 void push_back(const T& value); void emplace_back(const T& value); std::vector<int> vec1; vec1.push_back(10); // 复制或移动整数10到vec的末尾 std::vector<std::pair<int, double>> vec2; vec2.emplace_back(1, 2.2); // 直接在vec的末尾构造pair<int, double>(1, 2.2)push_back和emplace_back的性能差异主要体现在元素的构造和复制/移动开销上。- 对于简单数据类型(如int、double等),两者的性能差异可能不明显,因为简单数据类型的复制/移动开销相对较小。
- 但对于复杂数据类型(如自定义的类、结构体,尤其是包含动态内存分配的类型),emplace_back的性能优势则可能更加显著。
#include <vector> #include <string> #include <iostream> class MyClass { public: std::string name; int value; MyClass(std::string n, int v) : name(std::move(n)), value(v) { std::cout << "MyClass constructed" << std::endl; } MyClass(const MyClass& other) : name(other.name), value(other.value) { std::cout << "MyClass copied" << std::endl; } MyClass(MyClass&& other) noexcept : name(std::move(other.name)), value(other.value) { std::cout << "MyClass moved" << std::endl; } }; int main() { std::vector<MyClass> vec; // 使用push_back vec.push_back(MyClass("Example", 1)); // 构造 + 复制/移动 // 使用emplace_back vec.emplace_back("Example", 2); // 直接构造 return 0; } -
insert():在指定位置插入一个或多个元素。//原型 iterator insert(iterator position, const T& value); // 在指定位置插入单个元素 void insert(iterator position, size_type n, const T& value); // 在指定位置插入多个相同元素 template <class InputIterator> void insert(iterator position, InputIterator first, InputIterator last); // 在指定位置插入一个范围内的元素 std::vector<int> nums = {1, 2, 3}; auto it = nums.begin() + 1; // 在第二个位置插入元素 nums.insert(it, 4); // 插入单个元素 nums.insert(it, 2, 5); // 插入多个相同元素 -
pop_back():删除vector末尾的元素。//原型 void pop_back(); std::vector<int> nums = {1, 2, 3}; nums.pop_back(); // 删除末尾元素,vector变为 {1, 2} -
erase():删除指定位置或范围内的元素。//原型 iterator erase(iterator position); // 删除单个元素 iterator erase(iterator first, iterator last); // 删除一个范围内的元素 std::vector<int> nums = {1, 2, 3, 4, 5}; auto it = nums.begin() + 2; // 删除第三个元素 nums.erase(it); nums.erase(nums.begin(), nums.begin() + 2); // 删除前两个元素,vector变为 {3, 4, 5} -
clear():删除容器中所有的元素。仅清空了大小,容量还是存在的。
底层实现细节
- vector是动态数组,所以和数组一样拥有一段连续的内存空间,并且起始地址不变。
- 因为vector地址空间是连续的,所以能高效的进行随机访问,时间复杂度为o(1)。
- 在vector中插入和删除元素,需要对现有元素进行复制、移动,时间复杂度为o(n)。
- 如果vector中存储的对象很大,或者构造函数复杂,那么插入等开销会很大。因为拷贝现有对象时需要调用拷贝构造函数。
- vector扩容原理 + 新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就会分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素。注意不是在原来空间后直接增加空间
- 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
- 不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)