祝大家五一假期快乐,今天我们在认识完string后,开始步入vector的章节学习。
废话少说,发车!!!
C++ vector 容器深入解析
1. vector 基本概念
什么是 vector?
std::vector是 C++ 标准模板库(STL)中最常用的动态数组容器。它可以动态调整大小,在运行时自动管理内存,提供了类似数组的随机访问功能,同时具有灵活的动态扩容能力。核心特性
动态数组:底层基于数组实现,支持随机访问
自动内存管理:自动分配和释放内存
连续内存存储:元素在内存中连续存储,提供良好的缓存局部性
模板类:可存储任意类型的元素
RAII 原则:自动管理资源
2.底层原理:三指针与内存布局
template<typename T> class vector { private: T* _start; // 起始指针:指向首元素 T* _finish; // 结束指针:指向最后一个元素的下一位 T* _end_of_storage; // 容量指针:指向已分配内存末尾 };
3. vector 的基本使用
// 创建空 vector vector<int> vec1; // 创建指定大小的 vector vector<int> vec2(10); // 10个元素,初始值为0 vector<int> vec3(10, 5); // 10个元素,每个都是5 // 从初始化列表创建 vector<int> vec4({1,2,3,4,5});//传参构造 vector<int> vec5 = {1, 2, 3, 4, 5};//构造+拷贝构造被优化为直接构造 vector<int> vec6{1, 2, 3, 4, 5}; // 拷贝构造 vector<int> vec7(vec4); // 从迭代器范围创建 int arr[] = {1, 2, 3, 4, 5}; vector<int> vec8(arr, arr + 5);
4.扩容机制
- 计算新容量:
- GCC(SGI STL):2 倍扩容(如 4→8→16)
- MSVC(VS):1.5 倍扩容(如 4→6→9)
- 申请新内存:分配新容量的连续内存块
- 拷贝旧元素:旧内存元素拷贝到新内存
- 释放旧内存:销毁旧内存块,更新三指针
- 迭代器失效:扩容后原迭代器 / 指针 / 引用全部失效(内存地址改变)
扩容实例
5. 常用操作(代码示例 + 解析)
1.构造与初始化
// 1. 默认构造:空vector vector<int> vec1; // 2. 指定大小:5个元素,默认初始化为0 vector<int> vec2(5); // 3. 指定大小+初始值:5个元素,值为10 vector<int> vec3(5, 10); // 4. 拷贝构造 vector<int> vec4(vec3); // 5. 初始化列表 vector<int> vec5 = {1,2,3,4,5}; // 6. 迭代器范围构造 vector<int> vec6(vec5.begin(), vec5.end());2. emplace
与string不同,具有emplace接口。
方法 作用 emplace_back()尾部原地构造 emplace()指定位置原地构造 emplace_front()头部原地构造(list、deque 支持) try_emplace()map 安全插入(C++17)
vector<pair<int, string>> vec; // 传统方式(创建临时对象 + 拷贝) vec.push_back(make_pair(1, "hello")); // emplace 方式(原地构造,无拷贝) vec.emplace_back(2, "world"); // 在迭代器位置原地构造元素 vec.emplace(vec.begin() + 2, 10, "test");3.遍历方式(3 种常用)
vector<int> vec = {1,2,3,4,5}; // 1. 下标访问(O(1),最常用) for(int i=0; i<vec.size(); i++) cout << vec[i] << " "; // 2. 迭代器访问(STL通用) for(vector<int>::iterator it=vec.begin(); it!=vec.end(); it++) cout << *it << " "; // 3. 范围for(C++11+,简洁) for(auto x : vec) cout << x << " ";4. 增删改查
1. 尾部操作(高效)
vector<int> vec; vec.push_back(1); // 尾部插入1 vec.push_back(2); vec.pop_back(); // 尾部删除(无返回值) cout << vec.back(); // 取尾部元素2. 中间插入 / 删除(低效)
vector<int> vec = {1,2,3,4,5}; vec.insert(vec.begin()+2, 10); // 下标2插入10 → {1,2,10,3,4,5} vec.erase(vec.begin()+3); // 删除下标3 → {1,2,10,4,5} vec.erase(vec.begin(), vec.begin()+2); // 批量删除 → {10,4,5}3. 容量管理(优化性能关键)
vector<int> vec; vec.reserve(100); // 预分配100空间,避免频繁扩容 vec.resize(50); // 改变size为50,多余元素删除,不足补0 vec.resize(60, 10); // size=60,新增元素初始化为10 vec.shrink_to_fit(); // 释放capacity>size的多余空间 vec.clear(); // 清空元素,size=0,capacity不变4. 元素访问
vector<int> vec = {1,2,3}; cout << vec[0]; // 下标访问,越界崩溃 cout << vec.at(1); // 安全访问,越界抛out_of_range异常 cout << vec.front(); // 首元素 cout << vec.back(); // 尾元素
6. 迭代器失效问题(避坑重点)
1. 失效场景
1.扩容时:
push_back/emplace_back导致扩容所有迭代器、指针、引用失效(内存地址改变)
2.中间插入 / 删除时:
insert/erase使当前位置及之后的迭代器失效(元素后移)2. 解决方法
1.预分配容量:
reserve(n)提前申请足够空间,避免扩容2.重新获取迭代器:插入 / 删除后重新调用
begin()/end()3.尾部操作优先:尽量用
push_back/pop_back,避免中间操作
7.C++ 结构化绑定(Structured Bindings)
结构化绑定(Structured Bindings),C++17 标准引入,允许将聚合类型的成员直接拆解为独立变量。
pair<int,string> p{1,"cpp"}; auto [id,str] = p; id = 666; // 只改副本,不影响原p auto& [id,str] = p; id = 999; // 直接修改 p.first const auto& [id,str] = p; // id = 100; 报错,只读
8.题目运用
1. 找单身狗
136. 只出现一次的数字
https://leetcode.cn/problems/single-number/
给你一个 非空 整数数组
nums,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
class Solution { public: int singleNumber(vector<int>& nums) { int c=0; for(auto ch:nums) { c^=ch; } return c; } };
2. 杨辉三角形
118. 杨辉三角
https://leetcode.cn/problems/pascals-triangle/
**给定一个非负整数
numRows, 生成「杨辉三角」的前numRows行。在「杨辉三角」中,每个数是它左上方和右上方的数的和
class Solution { public: vector<vector<int>> generate(int numRows) { vector<vector<int>> vv(numRows); for(int i=0;i<numRows;i++) { vv[i].resize(i+1,1); } for(int j=2;j<numRows;j++) { for(int h=1;h<j;h++) { vv[j][h]=vv[j-1][h-1]+vv[j-1][h]; } } return vv; } };
3.删除数组中重复的项
26. 删除有序数组中的重复项
https://leetcode.cn/problems/remove-duplicates-from-sorted-array/
给你一个 非严格递增排列 的数组
nums,请你原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回nums中唯一元素的个数。考虑
nums的唯一元素的数量为k。去重后,返回唯一元素的数量k。
nums的前k个元素应包含 排序后 的唯一数字。下标k - 1之后的剩余元素可以忽略
class Solution { public: int removeDuplicates(vector<int>& nums) { if(nums.size()==0) return 0; int left=0,right=0; for(right;right<nums.size();right++) { if(nums[left]!=nums[right]) { nums[++left]=nums[right]; } } return left+1; } };
4.数组中出现次数超过一半的数字
**给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。**例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
数据范围:n≤50000n≤50000,数组中元素的值 0≤val≤100000≤val≤10000
要求:空间复杂度:O(1)O(1),时间复杂度 O(n)O(n)
1.排序法
题目要求我们找出现次数大于数组长度一半的数字,想到如果将input数组排序后,会变成怎样?对于示例input数组排序后结果,我们可以观察到,大于数组长度一半的元素一定分布在数组的中间。
int MoreThanHalfNum_Solution(vector<int>& numbers) { int s=numbers.size(); int s2=s/2; if(s==0) return 0; sort(numbers.begin(),numbers.end()); return numbers[s2]; }2.Boyer-Moore 投票算法
想象一下,如果把这些数字当做人种,一个数字和另外一个数字打了起来,同归于尽。最后剩下的是不是人数最多的那种人。这里要满足一个条件:某类人的数目一定要大于总人数的一半。
算法步骤:我们选择输入数组中第一个元素作为候选元素candidate,并设置其出现次数为count=1。随后遍历数组。当遇到与candidate相同的元素,count+1;不同的元素,count-1。当count为0的时候,选择下一个元素为候选元素,并且置count=1。遍历到数组的最后,剩下的candidate就是要求的结果
int MoreThanHalfNum_Solution(vector<int> &numbers) { int candidate = numbers[0]; int count = 1; for (int i = 1; i < numbers.size(); i++) { if (numbers[i] == candidate) { count++; } else { count--; } if (count == 0) { candidate = numbers[i + 1]; count++; } } return candidate; }






