C++ STL篇(四)------vector
还在被固定数组限制?vector前来救场!本篇带你看懂vector常用接口,新手也能轻松上手!话不多说,我们发车啦!ദ്ദി˶ー̀֊ー́ )✧
文章目录
- [C++ STL篇(四)------vector](#C++ STL篇(四)——vector)
-
- [1. vector 是什么?](#1. vector 是什么?)
- [2. 创建 vector 的 N 种姿势(构造函数)](#2. 创建 vector 的 N 种姿势(构造函数))
- [3. 遍历 vector 的三种经典方式](#3. 遍历 vector 的三种经典方式)
- [4. 容量管理](#4. 容量管理)
-
- [4.1 size 和 capacity](#4.1 size 和 capacity)
- [4.2 reserve:预定空间,但不改变 size](#4.2 reserve:预定空间,但不改变 size)
- [5. 增删改查:最常用的修改操作](#5. 增删改查:最常用的修改操作)
-
- [5.1 push_back:尾部追加](#5.1 push_back:尾部追加)
- [5.2 insert:任意位置插入](#5.2 insert:任意位置插入)
- [5.3 erase:删除元素](#5.3 erase:删除元素)
- [6. 用 vector 优雅地输入输出](#6. 用 vector 优雅地输入输出)
- [7. vector 存放复杂类型与二维数组](#7. vector 存放复杂类型与二维数组)
-
- [7.1 存放 string](#7.1 存放 string)
- [7.2 二维 vector(重点!)](#7.2 二维 vector(重点!))
- 结语:
1. vector 是什么?
简单说,vector 是 C++ 标准模板库(STL)中的一个序列容器,可以把它理解成"可以动态改变大小的数组"。它背后是一块连续的内存空间,所以支持用下标 [] 快速访问元素,同时还提供了丰富的成员函数,帮你完成增删改查。
要使用 vector,必须包含头文件:
cpp
#include <vector>
2. 创建 vector 的 N 种姿势(构造函数)

先看一段代码,我们逐行解释:
cpp
vector<int> v1; // 1. 空容器,里面啥也没有
vector<int> v2(10, 1); // 2. 10个元素,每个都是1
vector<int> v3(++v2.begin(), --v2.end()); // 3. 用迭代器区间构造
1. 默认构造 vector v1;
这个最常见,创建一个空 vector,size 和 capacity 都是 0。后面可以用 push_back 慢慢加元素。
2. 填充构造 vector v2(10, 1);
创建 10 个元素的 vector,每个元素初始化为 1。等价于你写了一个长度为 10 的数组,并且全都赋值为 1。如果只写 vector<int> v2(10);,那么 10 个元素会被默认初始化,对于 int 类型就是 0。
3. 迭代器区间构造 vector v3(++v2.begin(), --v2.end());
这个稍微高级一点,但非常实用。v2.begin() 返回指向第一个元素的迭代器,++ 后变成指向第二个元素;v2.end() 返回指向最后一个元素下一个位置的迭代器,-- 后指向最后一个元素。所以这一句的意思是:用 v2 的第 2 个到倒数第 2 个元素来初始化 v3。
结果: v2 是 10 个 1,去掉一头一尾,v3 就包含 8 个 1。
3. 遍历 vector 的三种经典方式
创建完 vector,总得看看里面有什么。
cpp
// 假设 v3 已经有 8 个元素,值都是 1
// 方式1:用下标,像数组一样
for (size_t i = 0; i < v3.size(); i++)
{
cout << v3[i] << " ";
}
cout << endl;
// 方式2:用迭代器,指针风格
vector<int>::iterator it = v3.begin();
while (it != v3.end())
{
cout << *it << " "; // *it 解引用,取迭代器指向的值
it++;
}
cout << endl;
// 方式3:C++11 范围 for 循环,最简洁!
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
输出:

三种方式对比:
- 下标法: 直观,但不适用于所有容器。
- 迭代器法: 通用性强,所有 STL 容器都支持,而且可以通过
*it修改元素。 - 范围 for: 简洁优雅,强烈推荐日常使用。注意如果用
auto& e可以修改原元素,避免拷贝。
4. 容量管理
4.1 size 和 capacity

size : 表示当前容器里实际拥有的元素个数

capacity: 表示vector当前已经分配好的内存空间最多能存多少元素
扩容机制演示:
cpp
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "capacity change:" << sz << endl;
cout << "making v grow:" << endl;
for (int i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity change:" << sz << endl;
}
}
}
在VS下的输出:

在 VS 编译器中,扩容通常是 1.5 倍增长;在 GCC 中则是 2 倍增长。原因是为了在"减少扩容次数 "和"避免内存浪费"之间取得平衡。
4.2 reserve:预定空间,但不改变 size

cpp
vector<int> v(10, 1); // size=10, capacity=10
v.reserve(20); // 申请至少能容纳20个元素的空间
cout << v.size() << endl; // 输出 10,size 不变!
cout << v.capacity() << endl; // 输出 20
reserve(n) 只会增大 capacity,不会添加元素,所以 size 不变。如果你提前知道要存多少数据,用 reserve 一次性开好空间,可以避免多次扩容带来的拷贝开销,提升性能。
reserve 不会缩容
cpp
v.reserve(5); // 试图缩小容量
cout << v.capacity() << endl; // 依然是 20,不会变小!
C++ 标准规定,reserve 只保证容量 >= n,不会主动缩容。
resize:改变 size,可能引发扩容
cpp
vector<int> v(10, 1);
v.resize(30, 2); // 增加到30个元素,新增的元素用 2 填充
cout << v.size() << endl; // 30
cout << v.capacity() << endl; // >=30,可能触发了扩容
v.resize(5); // 减少到5个元素,多出的被删除
cout << v.size() << endl; // 5
cout << v.capacity() << endl; // 容量不变,不会缩容!
总结一下:
操作 影响 size 影响 capacity 备注 push_back +1 不够时扩容 最常用添加方式 reserve(n) 不变 增大至 >= n 不会减小 resize(n) 变为 n n>capacity时扩容多余元素删除/默认填充
5. 增删改查:最常用的修改操作
5.1 push_back:尾部追加
cpp
vector<int> v(10, 1); // 10个1
v.push_back(3); // 现在变成11个元素:1,1,...1,3
简单粗暴,往最后面加一个元素。如果容量不够,自动扩容。
5.2 insert:任意位置插入
cpp
v.insert(v.begin(), 2); // 在开头插入 2
v.insert(v.begin() + 3, 6); // 在第4个位置(下标3)插入 6
insert(pos, val) 在 pos 位置之前插入 val。
注意:
pos是迭代器,不是下标。- 插入后,后面的所有元素都要向后移动,时间复杂度 O(n),所以如果频繁在中间插入,
vector并不是最佳选择。
5.3 erase:删除元素
cpp
v.erase(v.begin()); // 删除第一个元素
erase(pos) 删除 pos 位置的元素,返回下一个元素的迭代器。同样,删除后所有后面的元素要向前移动,效率较低。
完整示例:
cpp
vector<int> v(10, 1);
v.push_back(3);
v.insert(v.begin(), 2);
v.insert(v.begin() + 3, 6);
// 现在 v 内容:2,1,1,6,1,1,1,1,1,1,1,3
v.erase(v.begin());
// 删除了开头的2
迭代器失效问题
执行插入或删除操作后,原来的迭代器可能会失效!因为 vector 可能会重新分配内存,导致原来的迭代器指向的位置变得非法。安全做法是:每次操作后重新获取迭代器。
6. 用 vector 优雅地输入输出
很多人会这样写:
cpp
vector<int> v(5, 0);
for (size_t i = 0; i < v.size(); i++)
{
cin >> v[i];
}
这完全没问题。但更推荐用范围 for 输出:
cpp
for (auto e : v)
{
cout << e << " ";
}
如果要在遍历时修改元素值,记得用引用 !
cpp
for (auto& e : v)
{
e *= 2; // 每个元素翻倍
}
7. vector 存放复杂类型与二维数组
7.1 存放 string
cpp
vector<string> v1;
string s1("xxxx");
v1.push_back(s1); // 拷贝一份存入
v1.push_back("yyyyyy"); // 直接传入字符串字面值
// 遍历时强烈建议用 const 引用,避免不必要的拷贝!
for (const auto& e : v1)
{
cout << e << " ";
}
7.2 二维 vector(重点!)
二维数组经常用来表示矩阵、棋盘等。用 vector 创建二维数组非常简单:
cpp
vector<int> v(5, 1); // 一维,5个1
vector<vector<int>> vv(10, v); // 二维,10行,每行是一个 v
vv[2][1] = 2; // 修改第3行第2列的元素
// 遍历输出
for (size_t i = 0; i < vv.size(); i++)
{
for (size_t j = 0; j < vv[i].size(); j++)
{
cout << vv[i][j] << " ";
}
cout << endl;
}
这里 vv 就是一个 10 行 5 列的矩阵,每个元素初始值为 1。访问方式就是 vv[行][列],和原生二维数组一模一样。
这里我们看一个题:
ど°0°う♡ 传送门

解答示例:
cpp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
//创建一个二维vector,有numRows行,每行初始为空
vector<vector<int>> vv(numRows);
for(size_t i = 0;i<numRows;++i)
{
vv[i].resize(i+1,1);//将第i行的元素个数改为i+1,新增元素全部初始化为1
}
for(int i = 2;i<vv.size();++i)//从第二行开始计算
{
for(int j = 1;j<vv[i].size()-1;++j)//不需要计算每行的首尾了,它们已经是1了
{
vv[i][j] = vv[i-1][j] + vv[i-1][j-1];
}
}
return vv;
}
};
结语:
今天的内容到这里就结束了,希望你能有所收获~
代码无bug,学习不迷路,我们下篇再见! (•̀ᴗ•́)و