
✨ 把代码写进星轨,
用逻辑丈量宇宙。
| 导航 | 链接 |
|---|---|
| 个人主页 | 🏠 星轨初途 |
| 基础语言专栏 | 💻 C语言 、 📚 数据结构 |
| C++ 进阶专栏 | 🏆 C++学习(竞赛类) 、 ⚙️ C++专栏(开发类) |
| 刷题实战专栏 | 🚀 算法及编程题分享 |

文章目录
- [一、什么是 vector?](#一、什么是 vector?)
- 二、迭代器的使用
- [三、vector 的几种构造方式](#三、vector 的几种构造方式)
- [四、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++
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// 使用初始化列表创建一个包含 6 个整数的 vector
vector<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_iterator
vector<int>::reverse_iterator it2 = arr.rbegin();
while (it2 != arr.rend())
{
cout << *it2 << " ";
// 反向迭代器执行 ++ 操作时,实际上是向容器的"头部"移动
++it2;
}
cout << endl; // 预期输出: 6 5 4 3 2 1
return 0;
}

三、vector 的几种构造方式
vector 提供了极其灵活的构造方式,以下四种是我们在实际开发中最常用的:
| 构造函数声明 (Constructor) | 接口说明 |
|---|---|
vector() (重点) |
无参构造:构造一个空的动态数组。 |
vector(size_type n, const value_type& val = value_type()) |
填充构造 :构造并初始化 n n n 个值为 val 的元素。 |
vector(const vector& x) (重点) |
拷贝构造 :用另一个 vector 深拷贝初始化自己。 |
vector(InputIterator first, InputIterator last) |
迭代器区间构造 :使用一段左闭右开区间 [first, last) 拷贝数据。 |
实战代码演示:
C++
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 辅助打印函数
void print(const vector<int>& v, const string& name)
{
cout << name << ": ";
for (auto e : v) cout << e << " ";
cout << "\n";
}
int main()
{
// 1. 无参构造:创建一个空的 vector,常配合 push_back 使用
vector<int> v1;
v1.push_back(100);
// 2. 填充构造:开辟 5 个空间,并将每个元素初始化为 8
vector<int> v2(5, 8);
// 3. 拷贝构造:用 v2 深拷贝出一个全新的 v3
vector<int> v3(v2);
// 4. 迭代器区间构造:传入左闭右开区间 [first, last) 拷贝数据
int arr[] = { 1, 2, 3, 4, 5 };
vector<int> v4(arr, arr + 5);
// 打印验证
print(v1, "v1 (无参构造)");
print(v2, "v2 (填充构造)");
print(v3, "v3 (拷贝构造)");
print(v4, "v4 (区间构造)");
return 0;
}

四、vector 空间增长问题
容量管理是 vector 的核心。掌握以下接口,能帮你写出性能更高、内存更安全的代码。
| 容量接口 | 接口说明 |
|---|---|
size() |
获取当前有效数据的个数。 |
capacity() |
获取当前底层的总容量大小。 |
empty() |
判断容器是否为空(size == 0)。 |
resize(n) (重点) |
改变 vector 的有效数据个数 (size)。若放大则填充默认值,若缩小则截断。 |
reserve(n) (重点) |
提前开辟空间,仅改变 vector 的底层容量 (capacity),不改变有效数据个数。 |
实战代码演示:
C++
cpp
#include <iostream>
#include <vector>
using namespace std;
int main()
{
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);
return 0;
}

💡 避坑小结:
reservevsresize
reserve是只买房不住人 :扩充了容量,但里面没有有效数据,不能直接用[]访问。resize是既买房又住人 :不仅扩充了容量,还会往里面填入默认数据,可以直接用[]访问。
五、vector 增删查改
日常开发中最离不开的就是对数据的修改。需要特别注意的是,STL 将"查找"功能剥离了出去。
| 接口名称 | 接口说明 |
|---|---|
push_back (重点) |
尾部插入元素(效率最高)。 |
pop_back (重点) |
尾部删除元素。 |
find |
查找特定元素。 (注意:它是 <algorithm> 库里的全局算法函数,不是 vector 的成员接口)。 |
insert |
在指定的迭代器 position 之前插入元素。 |
erase |
删除指定的迭代器 position 位置的元素。 |
swap |
极速交换两个 vector 的底层数据空间。 |
operator[] (重点) |
像原生数组一样,通过下标随机访问元素。 |
实战代码演示:
C++
cpp
#include <iostream>
#include <vector>
#include <algorithm> // 使用 find 必须包含此算法库头文件
using namespace std;
int main()
{
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()
auto it = 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}
return 0;
}
六、总结与展望
通过对 vector 核心接口的学习,我们可以清晰地感受到 C++ STL 在设计上的优雅与高效:
- 告别手动内存管理 :
vector作为动态数组,完美接管了底层空间的扩容与数据迁移,彻底终结了 C 语言时代原生数组"定长不可变"的痛点,极大提升了开发效率与代码安全性。 - 精准的空间控制 :深刻理解
size(有效数据个数)与capacity(底层总容量)的区别,是vector进阶使用的关键。在已知数据规模的情况下,善用reserve提前开辟空间,能有效避免频繁的内存搬家,榨干程序的最后一滴性能。 - 算法与容器的分离 :STL 将
find等通用逻辑剥离到了<algorithm>算法库中,通过**迭代器(Iterator)**作为桥梁与容器无缝连接,完美展现了泛型编程(Generic Programming)解耦合的魅力。
🎯 下期预告:
掌握 vector 的接口用法,仅仅是我们征服 STL 的第一步。"只会开车不懂修车"是不够的。在下一篇文章中,我们将深入底层源码,从零开始手写模拟实现一个 vector 容器!
我们将抛开表面的魔法,去亲眼看看底层的 _start、_finish 和 _end_of_storage 这三个原生指针是如何相互配合完成扩容与增删查改的,敬请期待!