一、C++
vector
容器介绍1. STL 容器概览
C++ STL 涵盖多种容器,分顺序容器如 (
vector
)与关联容器(如map
、set
)。vector 作为常用顺序容器,是动态数组,运行时可自动扩容量且随机访问高效。2. 选用 vector 缘由
- 动态伸缩:对比 C 风格数组大小固定,vector 能自动管理内存,按需调整大小。如
vector<int> v = {1, 2, 3, 4, 5};
自动处理内存。- 接口多样:提供插入、删除、查找等丰富接口,像
push_back
添元素,erase
删元素,处理数据灵活。- 内存管理佳:C 风格数组易越界,vector 有内置机制防越界,保障程序稳定。
3. vector 与 C语言数组 对比
使用 C 风格数组的代码:
int arr[5] = {1, 2, 3, 4, 5};
与之相比,使用
vector
的方式更加灵活:#include <vector> using namespace std; vector<int> v = {1, 2, 3, 4, 5}; // 自动管理内存和大小
4. vector 利弊分析
- 优点:动态扩缩容,随机访问便捷,多数操作效率优,尾部操作性能好。
- 缺点:头部插入与删除时,因需移动大量元素致速度慢。
二、
vector
的使用1.
vector
的构造方法C++
vector
提供了多种构造方法,可以创建不同初始状态的vector
对象。
构造函数声明(constructor) 接口说明(功能) vector()(重点) 构造一个空的 vector
vector(size_type n, const T& val) 构造包含 n
个元素值为val
的vector
vector(const vector& v)(重点) 拷贝构造 vector
,与已有vector
一致vector(initializer_list<T>) 使用初始化列表构造 vector
代码演示:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v1; // 空 vector vector<int> v2(5, 100); // 5个100的元素 vector<int> v3(v2); // 拷贝构造 vector<int> v4 = {1, 2, 3, 4, 5}; // 使用初始化列表 for (int i : v4) { cout << i << " "; // 输出: 1 2 3 4 5 } return 0; }
2.
vector
容量与大小操作C++
vector
提供了多种方法管理容器的容量与大小。
方法名 功能描述 size() 返回当前元素个数 capacity() 返回分配的存储空间大小 empty() 判断容器是否为空 resize(n) 将容器大小调整为 n
,多出的部分用默认值填充reserve(n) 预留存储空间,避免多次扩容 shrink_to_fit() 收缩 capacity
以匹配size()
的大小代码演示:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1, 2, 3, 4, 5}; cout << "Size: " << v.size() << endl; // 当前元素个数 cout << "Capacity: " << v.capacity() << endl; // 当前容量 v.resize(10, 100); // 调整大小到10 cout << "After resize: " << v.size() << endl; v.reserve(20); // 预留空间 cout << "Capacity after reserve: " << v.capacity() << endl; v.shrink_to_fit(); // 收缩容量 cout << "Capacity after shrink_to_fit: " << v.capacity() << endl; return 0; }
动态扩展与性能问题
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2 倍增长的。这个问题经常会考察,不要固化的认为,vector 增容都是2倍,具体增长多少是 根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代 价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size。
通过以下代码测试 vector 的默认扩容机制
// 测试vector的默认扩容机制 void TestVectorExpand() { size_t sz; vector<int> v; sz = v.capacity(); cout << "making v grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } }
g++ 运行结果示例:
making foo grow: capacity changed: 1 capacity changed: 2 capacity changed: 4 capacity changed: 8 capacity changed: 16 capacity changed: 32 capacity changed: 64 capacity changed: 128
vs 运行结果示例:
making foo grow: capacity changed: 1 capacity changed: 2 capacity changed: 3 capacity changed: 4 capacity changed: 6 capacity changed: 9 capacity changed: 13 capacity changed: 19 capacity changed: 28 capacity changed: 42 capacity changed: 63 capacity changed: 94 capacity changed: 141
3.
vector
元素访问与修改
方法名 功能 operator[] 通过下标访问元素,不进行边界检查 at(n) 访问指定位置元素,进行越界检查 front() 返回第一个元素 back() 返回最后一个元素 data() 返回指向数组头部的指针 代码演示:
通过
operator[]
或at()
直接修改元素内容:v[0] = 10; v.at(2) = 20;
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1, 2, 3, 4, 5}; cout << "First element: " << v.front() << endl; // 访问第一个元素 cout << "Last element: " << v.back() << endl; // 访问最后一个元素 try { cout << v.at(10); // 越界访问 } catch (out_of_range& e) { cout << "Exception: " << e.what() << endl; }//异常捕获 return 0; }
4.vector
的插入、删除与修改(1)插入操作
vector
提供多种方法用于向容器中插入元素。
方法名 功能描述 push_back() 在末尾插入一个元素 insert() 在指定位置插入元素 emplace_back() 在末尾直接构造元素,避免不必要的复制开销 代码演练:
使用
push_back()
和insert()
插入元素#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1, 2, 3}; // 在末尾插入 v.push_back(4); // 在第二个位置插入元素5 v.insert(v.begin() + 1, 5); for (int val : v) { cout << val << " "; } return 0; }
emplace_back()
与push_back()
相比,直接在容器末尾构造元素,减少了不必要的临时对象生成。适用于复杂对象的插入场景。使用
emplace_back()
插入元素#include <iostream> #include <vector> using namespace std; struct Point { int x, y; Point(int a, int b) : x(a), y(b) {} }; int main() { vector<Point> points; // 直接在末尾构造对象 points.emplace_back(1, 2); cout << "Point: " << points[0].x << ", " << points[0].y << endl; return 0; }
(2) 删除操作
vector
提供了多种删除元素的方式,包括删除末尾元素和删除指定位置的元素。
方法名 功能描述 pop_back() 删除末尾元素 erase() 删除指定位置的元素或一段范围内的元素 clear() 清空整个 vector
代码演示:
使用
pop_back()
和erase()
删除元素#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1, 2, 3, 4, 5}; // 删除末尾元素 v.pop_back(); // 删除第一个元素 v.erase(v.begin()); for (int val : v) { cout << val << " "; } return 0; }
使用
clear()
清空vector
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = { 1, 2, 3, 4, 5 }; v.clear(); cout << "Vector size after clear: " << v.size() << endl; // 输出:0 return 0; }
(3)修改元素
代码演示:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1, 2, 3, 4, 5}; // 修改第二个元素 v[1] = 10; for (int val : v) { cout << val << " "; } return 0; }
三、
vector
的迭代器与遍历
vector
提供了多种迭代器类型,便于对元素进行遍历、修改或访问。
迭代器类型 功能 begin()(重点) 返回指向容器第一个元素的迭代器 end()(重点) 返回指向容器末尾的迭代器 rbegin()(重点) 返回指向容器最后一个元素的反向迭代器 rend()(重点) 返回指向容器第一个元素之前位置的迭代器 cbegin() 常量迭代器,无法修改元素 cend() 常量迭代器,返回指向末尾的常量迭代器 for_each()
(STL提供)对容器中的每个元素执行指定的操作 #include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {1, 2, 3, 4, 5}; // 使用正向迭代器遍历 for (auto it = v.begin(); it != v.end(); ++it) { cout << *it << " "; } cout << endl; // 使用反向迭代器遍历 for (auto rit = v.rbegin(); rit != v.rend(); ++rit) { cout << *rit << " "; } cout << endl; return 0; }
使用
for_each()
函数遍历并输出元素#include <iostream> #include <vector> #include <algorithm> using namespace std; void print(int val) { cout << val << " "; } int main() { vector<int> v = {1, 2, 3, 4, 5}; for_each(v.begin(), v.end(), print); // 使用for_each输出 return 0; }
四、
vector
迭代器失效问题1. 迭代器失效的原因
vector
迭代器失效的根本原因在于底层内存的重新分配或元素的移除,导致迭代器指向的内存不再有效。当发生迭代器失效时,继续使用该迭代器可能会引发未定义行为,如程序崩溃或访问错误数据。2. 常见导致迭代器失效的操作
扩容相关操作:当 vector 需要扩展容量时,会分配新的内存空间并将原有元素搬移到新的位置。此时,所有的迭代器将会失效。
- resize()
- reserve()
- insert()
- push_back()
- assign()
删除操作:删除操作会使指向被删除元素及其后续元素的迭代器失效。
(1)扩容操作导致迭代器失效
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v{1, 2, 3, 4, 5, 6}; auto it = v.begin(); // 扩容相关操作导致迭代器失效 v.resize(100, 8); // 扩容并填充元素 // v.reserve(100); // 扩容但不增加元素 // v.push_back(7); // 末尾插入可能引发扩容 // v.assign(100, 8); // 重新赋值并扩容 // 扩容后需要重新获取迭代器 it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; return 0; }
在每次扩容操作后,
vector
可能会分配新的内存空间,并释放原来的内存区域。这意味着之前的迭代器已指向失效的内存,因此在扩容操作后,必须重新获取迭代器。(2)删除操作导致迭代器失效
删除
vector
中的某些元素时,指向被删除元素及其后续元素的迭代器会失效。#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v{ 1, 2, 3, 4 }; // 查找元素3的迭代器 auto pos = find(v.begin(), v.end(), 3); // 删除元素3 v.erase(pos); // 迭代器失效,继续使用将导致程序崩溃或未定义行为 cout << *pos << endl; // 非法访问 return 0; }
删除某个元素后,指向该元素及其后续元素的迭代器会失效。在删除操作后应重新获取有效的迭代器,以避免出现非法访问或程序崩溃。
(3)删除偶数时的正确和错误写法
错误的删除写法在删除元素后没有正确更新迭代器,会导致迭代器失效,引发未定义行为。
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v{1, 2, 3, 4, 4}; auto it = v.begin(); // 错误的删除写法,迭代器未更新 while (it != v.end()) { if (*it % 2 == 0) { v.erase(it); // 迭代器失效,++it 导致未定义行为 } ++it; } return 0; }
分析:删除偶数元素时未更新迭代器,导致迭代器 “离家出走”,程序出现未定义行为。
正确示例:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v{1, 2, 3, 4, 4}; auto it = v.begin(); // 正确的删除写法 while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); // 返回新的有效迭代器,指向被删除元素的下一个元素 } else { ++it; } } for (int num : v) { cout << num << " "; } return 0; }
(4)编译器对迭代器失效的处理差异
不同编译器(如 GCC 和 MSVC)对迭代器失效的处理方式不同。GCC 在某些情况下可能会宽容处理失效迭代器,程序不会立即崩溃,但输出结果不确定;MSVC 则会直接抛出错误并导致程序崩溃。
GCC 下的宽松处理
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v{1, 2, 3, 4, 5}; auto it = find(v.begin(), v.end(), 3); // 删除元素3 v.erase(it); // 虽然迭代器失效,但在 GCC 下程序可能不会崩溃 cout << *it << endl; // 输出不确定 return 0; }
MSVC 下严格处理
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v{1, 2, 3, 4, 5}; auto it = find(v.begin(), v.end(), 3); // 删除元素3 v.erase(it); // 在 MSVC 下,使用失效迭代器会导致程序崩溃 cout << *it << endl; // 程序崩溃 return 0; }
(5)扩容后的迭代器失效问题
即使扩容后的程序在 Linux 环境下不会立刻崩溃,但输出结果仍然是不可靠的。以下代码展示了
vector
在reserve()
扩容后的迭代器失效问题。#include <iostream> #include <vector> using namespace std; int main() { vector<int> v{ 1, 2, 3, 4, 5 }; auto it = v.begin(); cout << "扩容之前,vector的容量为: " << v.capacity() << endl; // 通过 reserve 扩容 v.reserve(100); cout << "扩容之后,vector的容量为: " << v.capacity() << endl; // 迭代器失效,输出结果错误 while (it != v.end()) { cout << *it << " "; // 输出结果可能错误 ++it; } cout << endl; return 0; }
总结与建议
- 避免迭代器失效:进行可能引发迭代器失效的操作(如扩容、删除等)后,必须重新获取迭代器,以保证程序的稳定性。
- 最佳实践:对于 erase() 操作,使用函数返回的迭代器继续遍历,以避免出现迭代器失效问题。
- 编译器差异:不同编译器(如 GCC 和 MSVC)对迭代器失效的处理方式不同,在开发跨平台程序时应尤为注意。
标签:capacity,迭代,舞链,int,元素,C++,vector,include From: https://blog.csdn.net/zzy985/article/details/144645223五、相关文档
C++vector构造文档:https://cplusplus.com/reference/vector/vector/vector/
C++vector元素访问与修改文档:https://cplusplus.com/reference/vector/vector/at/
C++vector 容量与大小操作文档:https://cplusplus.com/reference/vector/vector/capacity/
C++vector插入、删除与修改文档:https://cplusplus.com/reference/vector/vector/erase/