当前位置: 首页 > news >正文

C++第九讲:vector

C++第九讲:vector

vector 是STL 中最常用的序列式容器,本质是一个动态数组,彻底解决了 C 语言静态数组大小固定、手动管理内存的痛点。它支持随机访问、自动扩容,是所有 C++ 开发者日常开发的首选容器,也是面试第一高频考点。


一、vector 简介

1. 什么是 vector

vector 是 C++ 标准库提供的动态数组容器,可以存储任意类型的元素,底层是一段连续的内存空间。

  • 自动管理内存:不需要手动申请 / 释放空间

  • 支持随机访问:通过[]像数组一样访问元素,时间复杂度 O (1)

  • 自动扩容:当空间不足时,自动申请更大的空间并拷贝元素

  • 丰富的接口:提供了增删查改等常用操作

2. 为什么不用 C 语言数组

对比项C 语言静态数组C 语言动态数组C++ vector
大小编译时固定,不能改变运行时可改,但手动管理自动扩容,无需手动管理
内存管理栈上自动释放堆上手动 malloc/free自动申请释放,无内存泄漏
越界检查无,越界可能崩溃部分编译器支持越界检查
接口无,需要自己实现提供丰富的增删查改接口

二、vector 常用接口(重点)

使用 vector 需要包含头文件:#include <vector>,所有接口都在std命名空间中。

1. 构造函数(4 个最常用)

构造函数功能说明示例
vector<T>()无参构造,创建空 vectorvector<int> v1;
vector<T>(size_t n, const T& val = T())构造 n 个值为 val 的元素vector<int> v2(5, 10); // 5个10
vector<T>(const vector<T>& v)拷贝构造vector<int> v3(v2);
vector<T>(InputIterator first, InputIterator last)用迭代器区间构造vector<int> v4(v2.begin(), v2.end());

代码示例

#include <iostream> #include <vector> using namespace std; ​ int main() { vector<int> v1; // 空vector vector<int> v2(5, 10); // 5个10 vector<int> v3(v2); // 拷贝v2 vector<int> v4(v2.begin(), v2.begin()+3); // 前3个元素:10,10,10 ​ // 用数组构造 int arr[] = {1,2,3,4,5}; vector<int> v5(arr, arr+sizeof(arr)/sizeof(int)); ​ return 0; }

2. 容量操作(面试高频)

函数功能说明注意事项
size_t size() const返回有效元素个数
size_t capacity() const返回总容量(能存多少元素)容量≥size
bool empty() const判断是否为空空返回 true
void reserve(size_t n)预留 n 个元素的空间✅ 只改容量,不改 size;提前预留避免频繁扩容
void resize(size_t n, const T& val = T())把有效元素改为 n 个n>size:用 val 填充;n<size:截断;可能改变容量

核心考点:vector 的扩容机制

当 vector 的 size 达到 capacity 时,再插入元素会触发自动扩容

  1. 申请一块更大的新空间(VS 按1.5 倍扩容,G++ 按2 倍扩容)

  2. 将旧空间的元素拷贝到新空间

  3. 释放旧空间

  4. 更新指针指向新空间

代码验证扩容倍数

void TestExpand() { vector<int> v; size_t sz = v.capacity(); cout << "初始容量:" << sz << endl; for (int i=0; i<100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "扩容到:" << sz << endl; } } }
  • VS 输出:1→2→3→4→6→9→13→19→28→42→63→94→141(1.5 倍)

  • G++ 输出:1→2→4→8→16→32→64→128(2 倍)

优化技巧:提前 reserve 预留空间

如果知道大概要存储多少元素,提前用reserve预留空间,避免频繁扩容(扩容会拷贝元素,效率低):

vector<int> v; v.reserve(100); // 提前预留100个元素空间 for (int i=0; i<100; ++i) { v.push_back(i); // 不会触发扩容 }

3. 增删查改操作

函数功能说明时间复杂度
void push_back(const T& val)尾插元素O (1)(扩容时 O (n))
void pop_back()尾删元素O(1)
iterator insert(iterator pos, const T& val)在 pos 位置插入 valO (n)(需要搬移元素)
iterator erase(iterator pos)删除 pos 位置的元素O (n)(需要搬移元素)
void swap(vector<T>& v)交换两个 vector 的底层空间O (1)(只交换指针)
T& operator[](size_t pos)访问 pos 位置的元素O(1)

注意:find 不是 vector 的成员函数

查找元素需要使用<algorithm>头文件中的find算法:

#include <algorithm> vector<int> v = {1,2,3,4,5}; // 查找3,返回迭代器,找不到返回v.end() auto it = find(v.begin(), v.end(), 3); if (it != v.end()) { cout << "找到了:" << *it << endl; }

4. 迭代器

vector 的迭代器本质是原生指针,支持 ++、--、*、-> 等操作。

迭代器功能说明
begin()/end()正向迭代器:begin 指向第一个元素,end 指向最后一个元素的下一个位置
rbegin()/rend()反向迭代器:rbegin 指向最后一个元素,rend 指向第一个元素的前一个位置
cbegin()/cend()const 正向迭代器(只读)

代码示例:三种遍历方式

int main() { vector<int> v = {1,2,3,4,5}; ​ // 1. []访问(推荐,最简洁) for (int i=0; i<v.size(); ++i) { cout << v[i] << " "; } cout << endl; ​ // 2. 迭代器 vector<int>::iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; ​ // 3. 范围for(C++11推荐) for (auto e : v) { cout << e << " "; } cout << endl; ​ return 0; }

三、核心难点:迭代器失效(面试必考)

1. 什么是迭代器失效

vector 的迭代器本质是指向底层数组的指针,当底层空间被释放元素位置发生改变时,原来的迭代器就会变成野指针,继续使用会导致程序崩溃或结果错误。

2. 两种导致迭代器失效的场景

场景 1:底层空间改变(扩容)

所有会导致扩容的操作都会使迭代器失效:reserveresizeinsertpush_backassign等。

错误示例
int main() { vector<int> v = {1,2,3,4,5}; auto it = v.begin(); cout << "扩容前容量:" << v.capacity() << endl; // 5 ​ // 触发扩容,旧空间被释放,it变成野指针 v.reserve(100); cout << "扩容后容量:" << v.capacity() << endl; // 100 ​ // 错误:使用失效的迭代器,VS下直接崩溃,G++下结果错误 while (it != v.end()) { cout << *it << " "; ++it; } return 0; }

场景 2:指定位置删除元素(erase)

删除 pos 位置的元素后,pos 后面的元素会往前搬移,导致pos 及之后的迭代器失效(VS 下严格检测,G++ 下部分情况可能运行但结果错误)。

错误示例:删除所有偶数
// 错误写法 int main() { vector<int> v = {1,2,3,4}; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { v.erase(it); // 删除后it失效 } ++it; // 访问失效的迭代器,崩溃 } return 0; }
正确写法

erase会返回删除元素的下一个位置的迭代器,用这个返回值更新 it:

// 正确写法 int main() { vector<int> v = {1,2,3,4}; auto it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); // 用返回值更新it } else { ++it; } } return 0; }

3. 迭代器失效的解决方法

所有可能导致迭代器失效的操作后,重新获取迭代器

  • 扩容后:重新调用begin()获取新的迭代器

  • erase 后:使用erase返回的迭代器


四、vector 底层原理与模拟实现

1. 底层结构

vector 的底层非常简单,只有三个指针

  • _start:指向数组的起始位置

  • _finish:指向最后一个有效元素的下一个位置

  • _endofstorage:指向数组容量的末尾位置

template<class T> class vector { private: T* _start; T* _finish; T* _endofstorage; };

核心关系

  • size() = _finish - _start

  • capacity() = _endofstorage - _start

2. 模拟实现核心函数

2.1 构造与析构

template<class T> class vector { public: // 无参构造 vector() : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) {} // 构造n个val vector(size_t n, const T& val = T()) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(n); for (size_t i=0; i<n; ++i) { push_back(val); } } // 拷贝构造(深拷贝) vector(const vector<T>& v) : _start(nullptr) , _finish(nullptr) , _endofstorage(nullptr) { reserve(v.capacity()); for (const auto& e : v) { push_back(e); } } // 赋值运算符重载(现代版) vector<T>& operator=(vector<T> v) { swap(v); return *this; } // 析构函数 ~vector() { if (_start) { delete[] _start; _start = _finish = _endofstorage = nullptr; } } // 交换两个vector void swap(vector<T>& v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_endofstorage, v._endofstorage); } // ... 其他成员函数 private: T* _start; T* _finish; T* _endofstorage; };

2.2 容量操作:reserve

void reserve(size_t n) { if (n > capacity()) { size_t oldSize = size(); // 1. 申请新空间 T* tmp = new T[n]; // 2. 拷贝元素(不能用memcpy!自定义类型会浅拷贝) for (size_t i=0; i<oldSize; ++i) { tmp[i] = _start[i]; } // 3. 释放旧空间 delete[] _start; // 4. 更新指针 _start = tmp; _finish = _start + oldSize; _endofstorage = _start + n; } }

易错点:不能用 memcpy 拷贝自定义类型

memcpy 是浅拷贝,如果 vector 存储的是 string、vector 等自定义类型,memcpy 会导致多个对象共享同一块内存,析构时重复释放崩溃。必须用赋值运算符进行深拷贝。

2.3 尾插:push_back

void push_back(const T& val) { // 空间满了就扩容 if (_finish == _endofstorage) { size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(newCapacity); } // 尾插元素 *_finish = val; ++_finish; }

2.4 访问与迭代器

T& operator[](size_t pos) { assert(pos < size()); return _start[pos]; } const T& operator[](size_t pos) const { assert(pos < size()); return _start[pos]; } iterator begin() { return _start; } iterator end() { return _finish; }

五、动态二维数组:vector<vector<T>>

vector 可以嵌套使用,实现动态二维数组,比 C 语言的二维数组更灵活。

1. 原理

vector<vector<int>> vv(n)表示一个包含 n 个vector<int>的 vector,每个元素都是一个独立的 vector。

2. 示例:杨辉三角

class Solution { public: vector<vector<int>> generate(int numRows) { // 创建numRows行的二维数组 vector<vector<int>> vv(numRows); // 每行的元素个数等于行号+1,初始化为1 for (int i=0; i<numRows; ++i) { vv[i].resize(i+1, 1); } // 填充中间元素:第i行第j列 = 第i-1行第j列 + 第i-1行第j-1列 for (int i=2; i<numRows; ++i) { for (int j=1; j<i; ++j) { vv[i][j] = vv[i-1][j] + vv[i-1][j-1]; } } return vv; } };

六、经典 OJ 实战

1. 只出现一次的数字

// 要求:线性时间复杂度,不使用额外空间 class Solution { public: int singleNumber(vector<int>& nums) { int res = 0; for (int e : nums) { res ^= e; // 异或:相同为0,不同为1 } return res; } };

2. 删除排序数组中的重复项

// 要求:原地修改,空间复杂度O(1) class Solution { public: int removeDuplicates(vector<int>& nums) { if (nums.empty()) return 0; int slow = 0; for (int fast=1; fast<nums.size(); ++fast) { if (nums[fast] != nums[slow]) { nums[++slow] = nums[fast]; } } return slow + 1; } };

七、本章核心总结

  1. vector 是动态数组,底层是连续内存,支持随机访问,自动扩容

  2. 扩容机制:VS1.5 倍,G++2 倍,提前 reserve 可优化性能

  3. 迭代器失效:扩容和 erase 会导致失效,解决方法是操作后重新获取迭代器

  4. 模拟实现:核心是三个指针,深拷贝避免浅拷贝问题,不能用 memcpy 拷贝自定义类型

  5. 常用接口:push_back、pop_back、operator []、reserve、resize、迭代器

http://www.cnnetsun.cn/news/3926315.html

相关文章:

  • DVWA靶场环境搭建指南:从零部署Web漏洞测试平台
  • 终极指南:如何使用roop-unleashed实现零训练AI换脸
  • 福州看诊多动症注意力问题哪家医院靠谱
  • 做出一流的网站建设答辩ppt:资深策划人揭秘从逻辑构建到视觉呈现的终极指南
  • 用于自动驾驶和信号管理的多类别鸟瞰图道路场景数据集
  • 抖音批量下载神器:3分钟掌握无水印视频下载终极技巧
  • 西安微信网站建设公司哪家好?2024年企业转型必看的避坑指南与实战心得
  • Windows本地部署中文OpenClaw:从环境搭建到飞书机器人集成实战
  • 热电联供微网优化:Matlab两阶段随机规划实践
  • 从原理到实战:基于深度学习的AI音乐检测器构建指南
  • AI算力困境与解决方案:大模型时代的实战指南
  • Socket编程实战:TCP与UDP协议选择与应用
  • 如何免费扩展Windows工作空间:虚拟显示器驱动终极指南
  • Ansible 自动化运维实战 —— 批量部署、安全加固与进阶技巧
  • 3分钟学会:如何免费提取视频硬字幕生成SRT文件
  • Unity音频开发进阶:集成NAudio实现底层音频处理与实时控制
  • 3步快速搞定PMX转VRM:Blender插件完整解决方案
  • 如何快速掌握猫抓扩展:视频资源嗅探与下载的完整指南
  • 猫抓浏览器扩展:5步轻松下载网页视频的终极指南
  • 3分钟解锁B站视频解析:开发者必备的PHP API工具全解析
  • 3分钟快速上手:ncmdump终极免费NCM转MP3完整指南
  • 揭秘上海网站建设公司官网背后的真相与价值:如何通过专业定制网站为企业品牌赋能与业务增长提供坚实基石
  • Unity Input System虚拟摇杆开发:固定、跟随、灵活三模式实现详解
  • 终极UnityExplorer完整指南:5步掌握游戏实时调试技术
  • 基于Dify与RAG技术构建垂直领域智能问答助手实战指南
  • 松江工业区网站建设全解析:从零基础到获客高手的实战指南
  • VTJ.PRO v2.6.1 重磅发布:双代理(架构师+执行者)上线,AI低代码引擎迎来智能中枢
  • AI过度奉承削弱用户判断力?技术人如何构建健康人机交互
  • 3分钟掌握PPTX转HTML:浏览器内完成的无服务器解决方案
  • 终极猫抓资源嗅探指南:三步搞定网页视频音频下载