【C++进阶】vector 类从入门到精通:核心接口与内存机制实战指南
用逻辑丈量宇宙。
| 导航 | 链接 |
|---|---|
| 个人主页 | 🏠 星轨初途 |
| 基础语言专栏 | 💻 C语言 、 📚 数据结构 |
| C++ 进阶专栏 | 🏆 C++学习(竞赛类) 、 ⚙️ C++专栏(开发类) |
| 刷题实战专栏 | 🚀 算法及编程题分享 |
文章目录
- 一、什么是 vector?
- 二、迭代器的使用
- 三、vector 的几种构造方式
- 四、vector 空间增长问题
- 五、vector 增删查改
- 六、总结与展望
前言:
在前两篇文章中,我们详细讲解了 STL 库中的
string类。今天,我们将把目光转向另一个极其重要且常用的容器——vector。因为 STL 的设计具有高度的统一性,有了学习string的基础,你会发现攻克vector其实非常轻松!
一、什么是 vector?
在 C 语言阶段,我们最常用的基础容器是原生数组。然而,原生数组存在一个极其明显的缺陷:大小固定且缺乏内存管理能力。一旦在声明时确定了长度,后续如果数据量超出预期,开发者就必须手动执行“开辟新空间 -> 拷贝旧数据 -> 释放旧空间”的繁琐流程,这不仅增加了代码的冗余度,还极易引发内存泄漏或越界访问等安全隐患。
C++ 标准模板库(STL)中的vector完美解决了这一痛点。vector的本质是一个动态分配内存的顺序表(动态数组),它在保留了原生数组优势的同时,弥补了其在空间管理上的不足。
具体而言,vector支持并具备以下核心特性:
- 空间自动扩容(动态管理):当不断向容器中添加元素导致容量不足时,
vector会在底层自动申请一块更大的连续内存空间,并将原有数据安全迁移,彻底免去了开发者手动干预内存的烦恼。 - 极速的随机访问:由于其底层依然维护着一块物理上连续的内存空间,
vector支持像原生数组一样,通过operator[]或迭代器进行O ( 1 ) O(1)O(1)时间复杂度的极速下标访问。 - 丰富的成员接口:STL 为其封装了大量开箱即用的操作接口(如尾插
push_back、插入insert、删除erase、获取大小size等),极大提升了工程开发效率。 - 强大的泛型支持:作为类模板,
vector可以用来存储任意类型的数据,无论是基础类型(如vector<int>)、自定义类,还是嵌套容器(如vector<vector<int>>),都能完美兼容。
总而言之,在现代 C++ 开发中,vector是日常使用频率最高、最不可或缺的底层基础容器。
🔗官方文档参考:cplusplus.com - vector 学习
二、迭代器的使用
和string一样,vector最标准、最安全的遍历方式就是使用迭代器(Iterator)。
| 迭代器组合 | 适用场景 |
|---|---|
begin()/end() | 正向遍历:获取指向首元素的iterator,以及尾元素下一个位置(越界标志)的iterator。 |
rbegin()/rend() | 反向遍历:获取指向尾元素的reverse_iterator,以及首元素前一个位置的reverse_iterator。 |
实战代码演示:
C++
#include<iostream>#include<vector>usingnamespacestd;intmain(){// 使用初始化列表创建一个包含 6 个整数的 vectorvector<int>arr={1,2,3,4,5,6};// ==========================================// 1. 正向遍历 (Forward Iteration)// ==========================================vector<int>::iterator it1=arr.begin();// 循环条件:只要迭代器还没有到达末尾的越界位置while(it1!=arr.end()){cout<<*it1<<" ";// 解引用获取数据++it1;// 迭代器向前移动一步}cout<<endl;// 预期输出: 1 2 3 4 5 6// ==========================================// 2. 反向遍历 (Reverse Iteration)// ==========================================// 注意类型是 reverse_iteratorvector<int>::reverse_iterator it2=arr.rbegin();while(it2!=arr.rend()){cout<<*it2<<" ";// 反向迭代器执行 ++ 操作时,实际上是向容器的"头部"移动++it2;}cout<<endl;// 预期输出: 6 5 4 3 2 1return0;}三、vector 的几种构造方式
vector提供了极其灵活的构造方式,以下四种是我们在实际开发中最常用的:
| 构造函数声明 (Constructor) | 接口说明 |
|---|---|
vector() (重点) | 无参构造:构造一个空的动态数组。 |
vector(size_type n, const value_type& val = value_type()) | 填充构造:构造并初始化n nn个值为val的元素。 |
vector(const vector& x) (重点) | 拷贝构造:用另一个vector深拷贝初始化自己。 |
vector(InputIterator first, InputIterator last) | 迭代器区间构造:使用一段左闭右开区间[first, last)拷贝数据。 |
实战代码演示:
C++
#include<iostream>#include<vector>#include<string>usingnamespacestd;// 辅助打印函数voidprint(constvector<int>&v,conststring&name){cout<<name<<": ";for(autoe:v)cout<<e<<" ";cout<<"\n";}intmain(){// 1. 无参构造:创建一个空的 vector,常配合 push_back 使用vector<int>v1;v1.push_back(100);// 2. 填充构造:开辟 5 个空间,并将每个元素初始化为 8vector<int>v2(5,8);// 3. 拷贝构造:用 v2 深拷贝出一个全新的 v3vector<int>v3(v2);// 4. 迭代器区间构造:传入左闭右开区间 [first, last) 拷贝数据intarr[]={1,2,3,4,5};vector<int>v4(arr,arr+5);// 打印验证print(v1,"v1 (无参构造)");print(v2,"v2 (填充构造)");print(v3,"v3 (拷贝构造)");print(v4,"v4 (区间构造)");return0;}四、vector 空间增长问题
容量管理是vector的核心。掌握以下接口,能帮你写出性能更高、内存更安全的代码。
| 容量接口 | 接口说明 |
|---|---|
size() | 获取当前有效数据的个数。 |
capacity() | 获取当前底层的总容量大小。 |
empty() | 判断容器是否为空(size == 0)。 |
resize(n)(重点) | 改变vector的有效数据个数 (size)。若放大则填充默认值,若缩小则截断。 |
reserve(n)(重点) | 提前开辟空间,仅改变vector的底层容量 (capacity),不改变有效数据个数。 |
实战代码演示:
C++
#include<iostream>#include<vector>usingnamespacestd;intmain(){vector<int>v;// 1. reserve: 提前开辟空间,减少频繁扩容带来的性能开销v.reserve(10);v.push_back(1);v.push_back(2);// 2. size: 获取当前有效元素个数cout<<"Size: "<<v.size()<<endl;// 输出: 2// 3. capacity: 获取底层总容量cout<<"Capacity: "<<v.capacity()<<endl;// 输出: 10// 4. empty: 判空cout<<"Is empty? "<<(v.empty()?"Yes":"No")<<endl;// 输出: No// 5. resize: 改变有效元素个数// 若放大:多出的位置用 5 填充;若缩小:截断超出部分v.resize(5,5);return0;}💡避坑小结:
reservevsresize
reserve是只买房不住人:扩充了容量,但里面没有有效数据,不能直接用[]访问。resize是既买房又住人:不仅扩充了容量,还会往里面填入默认数据,可以直接用[]访问。
五、vector 增删查改
日常开发中最离不开的就是对数据的修改。需要特别注意的是,STL 将“查找”功能剥离了出去。
| 接口名称 | 接口说明 |
|---|---|
push_back(重点) | 尾部插入元素(效率最高)。 |
pop_back(重点) | 尾部删除元素。 |
find | 查找特定元素。(注意:它是<algorithm>库里的全局算法函数,不是vector的成员接口)。 |
insert | 在指定的迭代器position之前插入元素。 |
erase | 删除指定的迭代器position位置的元素。 |
swap | 极速交换两个vector的底层数据空间。 |
operator[](重点) | 像原生数组一样,通过下标随机访问元素。 |
实战代码演示:
C++
#include<iostream>#include<vector>#include<algorithm>// 使用 find 必须包含此算法库头文件usingnamespacestd;intmain(){vector<int>v={1,2,3};// 1. push_back: 尾插v.push_back(4);// v 变成: {1, 2, 3, 4}// 2. pop_back: 尾删v.pop_back();// v 变成: {1, 2, 3}// 3. operator[]: 下标访问并修改v[0]=10;// v 变成: {10, 2, 3}// 4. find: 查找元素 2 的位置// 返回值是一个迭代器,如果没有找到,则返回 v.end()autoit=find(v.begin(),v.end(),2);// 5. insert: 在指定位置前插入if(it!=v.end()){v.insert(it,20);// 在 2 之前插入 20 -> v 变成: {10, 20, 2, 3}}// 6. erase: 删除指定位置数据v.erase(v.begin());// 删除第一个数据 -> v 变成: {20, 2, 3}// 7. swap: 交换两个 vector 内容vector<int>v2={7,8};v.swap(v2);// 此时 v 变成 {7, 8}, v2 变成 {20, 2, 3}return0;}六、总结与展望
通过对vector核心接口的学习,我们可以清晰地感受到 C++ STL 在设计上的优雅与高效:
- 告别手动内存管理:
vector作为动态数组,完美接管了底层空间的扩容与数据迁移,彻底终结了 C 语言时代原生数组“定长不可变”的痛点,极大提升了开发效率与代码安全性。 - 精准的空间控制:深刻理解
size(有效数据个数)与capacity(底层总容量)的区别,是vector进阶使用的关键。在已知数据规模的情况下,善用reserve提前开辟空间,能有效避免频繁的内存搬家,榨干程序的最后一滴性能。 - 算法与容器的分离:STL 将
find等通用逻辑剥离到了<algorithm>算法库中,通过**迭代器(Iterator)**作为桥梁与容器无缝连接,完美展现了泛型编程(Generic Programming)解耦合的魅力。
🎯 下期预告:
掌握vector的接口用法,仅仅是我们征服 STL 的第一步。“只会开车不懂修车”是不够的。在下一篇文章中,我们将深入底层源码,从零开始手写模拟实现一个vector容器!
我们将抛开表面的魔法,去亲眼看看底层的_start、_finish和_end_of_storage这三个原生指针是如何相互配合完成扩容与增删查改的,敬请期待!
