【C++】vector 容器超全详解|新手入门学习(构造函数、迭代器、容量操作、增删查改,迭代器失效和二维vector)
文章目录
前言
C 语言里我们用数组,大小固定,想扩容就得手动realloc,又麻烦又容易出错。
到了 C++,有了vector—— 一个可以自动增长的动态数组,不用自己管内存,想加就加、想删就删,可以说非常方便。
本文主要讲解构造函数、迭代器、容量操作、增删查改,迭代器失效和二维vector等内容,帮助新手快速入门。
提示:以下是本篇文章正文内容,下面案例可供参考
一、vector 是什么?
vector 英文翻译过来叫“向量”,你可以把它理解成一个可以自动扩容的动态数组。
和普通数组比,vector有三大优势:
- 自动管理内存:不用手动申请释放内存,空间不够会自动扩容;
- 支持随机访问:和数组一样可以用方括号
[]下标访问; - 丰富的接口:插入、删除、查找、交换…… 全都有现成的。
使用 vector 需要包含头文件:
#include <vector>,且在std命名空间下。
二、vector 的构造函数
vector 有多种构造方式,最常用的有 4 种:
| 构造函数 | 功能说明 |
|---|---|
vector<T> v | 无参构造一个没有数据的 vector |
vector<T> v(n, val) | 构造一个有 n 个元素的值为 val 的 vector |
vector<T> v(v2) | 拷贝构造 |
vector<T> v(first, last) | 用迭代器区间构造 |
补充:vector<T> v(n, val)也可以不传 val,用vector<T> v(n)表示构造一个有 n 个元素的值为 0 的vector。T 表示传入的数据类型。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v1; // 空的int vector
//vector<int> v2(5); // 5个0
vector<int> v2(5, 10); // 5个10
vector<int> v3(v2); // 拷贝构造v3
vector<int> v4(v2.begin(), v2.end()); // 迭代器区间构造
// 还可以用数组构造
int arr[] = { 1, 2, 3, 4, 5 };
vector<int> v5(arr, arr + sizeof(arr) / sizeof(int));
return 0;
}
三、vector 迭代器
迭代器是 STL 的通用遍历方式,vector 的迭代器本质就是原生指针,用起来非常方便。
1、四种迭代器
| 迭代器 | 功能 |
|---|---|
begin() / end() | 正向迭代器,begin 指向第一个元素,end 指向最后一个元素的下一个位置 |
rbegin() / rend() | 反向迭代器,rbegin 指向最后一个元素,rend 指向第一个元素的前一个位置 |
cbegin() / cend() | const 正向迭代器,只读不可改 |
crbegin() / crend() | const 反向迭代器,也是只读不可改 |
注:end()、rend() 都是哨兵位迭代器,不指向有效元素,因此不能被解引用。
2、四种遍历方式
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// 方式1:下标[]遍历(最常用,像数组一样)
for (size_t 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:反向迭代器(从后往前)
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
// 方式4:范围for(最简洁的方法,是C++11新增的语法)
for (auto e : v)
cout << e << " ";
cout << endl;
return 0;
}

技巧点:用auto自动推导迭代器类型,不用写长长的vector<int>::iterator。
四、vector 容量操作
| 函数 | 功能说明 |
|---|---|
size() | 返回有效元素个数 |
capacity() | 返回底层总容量 |
empty() | 判断vector是否为空 |
resize(n, val) | 把有效元素个数改成 n,多出的用 val 填充 |
reserve(n) | 预留 n 个元素的空间,只改容量,不改有效个数 |
1、函数使用
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
cout << v.size() << endl; // 0
cout << v.capacity() << endl; // 0
cout << v.empty() << endl; // 1(空)
v.push_back(1);
v.push_back(2);
v.push_back(3);
cout << v.size() << endl; // 3
cout << v.capacity() << endl; // 容量(自动扩容,大于等于size)
// resize:改变有效元素个数
v.resize(5, 0); // 变成5个元素,新增的位置用0填充 → 1 2 3 0 0
v.resize(2); // 变成2个元素 → 1 2
cout << v.capacity() << endl; // 注意:resize只改size,capacity不会缩小
// reserve:只预留空间,不改size
v.reserve(100); // 容量变成100,但size还是2
cout << v.size() << endl; // 2
cout << v.capacity() << endl; // 100
return 0;
}

2、vector 的扩容机制(了解)
vector 空间不够时会自动扩容,但不同编译器扩容倍数不一样:
- VS(PJ 版 STL):按1.5 倍扩容
- G++(SGI 版 STL):按2 倍扩容
这里用 VS 来演示:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
size_t sz = v.capacity();
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 changed: " << sz << endl;
}
}
return 0;
}

3、优化效率技巧:提前 reserve
在使用vector处理大量数据时,如果知道大概要存多少个元素,提前用reserve预留空间,可以避免边插入边扩容,大幅提升效率。
int main() {
vector<int> v;
v.reserve(100); // 提前预留100个空间
for (int i = 0; i < 100; ++i)
v.push_back(i); // 全程不扩容,效率高
return 0;
}
五、vector 增删查改
1、接口总览
| 函数 | 功能说明 |
|---|---|
push_back(x) | 尾插一个元素(最常用) |
pop_back() | 尾删一个元素 |
insert(pos, x) | 在 pos 位置前插入元素 x |
erase(pos) | 删除 pos 位置的元素 |
swap(v) | 交换两个 vector 的内容 |
operator[] | 下标访问(最常用) |
find(first, last, x) | 查找元素(算法库函数,不是成员函数) |
注意:
find函数在<algorithm>头文件里,不是 vector 的成员函数!
2、代码使用
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
// 尾插
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
// v: 1 2 3 4
// 尾删
v.pop_back();
// v: 1 2 3
// 任意位置插入
v.insert(v.begin(), 0); // 开头插入0 → 0 1 2 3
v.insert(v.begin() + 2, 99); // 第3个位置插入99 → 0 1 99 2 3
// 任意位置删除
v.erase(v.begin()); // 删除第一个 → 1 99 2 3
v.erase(v.begin() + 1); // 删除第二个 → 1 2 3
// 查找(注意:find是算法库的,不是vector的成员函数)
auto pos = find(v.begin(), v.end(), 2);
if (pos != v.end())
cout << "找到了,下标:" << pos - v.begin() << endl;
// 交换
vector<int> v2(5, 10);
v.swap(v2); // 交换v和v2的内容
return 0;
}
六、大坑:vector 迭代器失效
1、什么是迭代器失效?
vector 的迭代器本质就是指针。迭代器失效 = 迭代器指向的空间被释放了,再用就会访问非法内存,程序崩溃。
2、两种导致失效的场景
场景 1:扩容导致失效(空间变了)
所有可能引起底层空间改变的操作,都可能导致迭代器失效:resize、reserve、insert、push_back、assign等。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{ 1,2,3,4,5 };
auto it = v.begin();
v.reserve(100); // 扩容!旧空间被释放,it失效了
// 再用it就是非法访问,程序崩溃
while (it != v.end())
{
cout << *it << " ";
++it;
}
return 0;
}
原因:扩容时开了新空间、释放了旧空间,而it还指向旧空间的地址。
场景 2:erase 删除导致失效(位置变了)
erase 删除元素后,后面的元素会往前搬移。如果删除的是最后一个元素,删完后 pos 刚好等于 end,就失效了。
以下是错误的写法:
#include <iostream>
#include <vector>
using namespace std;
// 错误写法:删除所有偶数,会崩溃或结果不对
int main()
{
vector<int> v{ 1,2,3,4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it); // erase后it失效了!
++it; // 失效的迭代器再++,会崩溃或者结果与预期不符!
}
return 0;
}
正确操作是:接收 erase 返回值,erase会返回下一个有效位置的迭代器,用返回值更新 it 就不会失效了。
以下是正确的写法:
#include <iostream>
#include <vector>
using namespace std;
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、迭代器失效的通用解决办法
操作完之后,如果还要用迭代器,重新赋值一下就好。
比如扩容之后,重新it = v.begin();,就不会有问题了。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v{ 1,2,3,4,5 };
auto it = v.begin();
v.reserve(100); // 扩容!旧空间被释放,it失效了
it = v.begin(); // 重新赋值
while (it != v.end())
{
cout << *it << " ";
++it;
}
return 0;
}
七、vector 二维数组(以杨辉三角为例)
vector 的元素也可以是 vector,这样就形成了二维数组,而且每行长度可以不一样,比 C 语言的二维数组灵活多了。
1、基本用法
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 3行的二维数组,每行是一个vector<int>
vector<vector<int>> vv(3);
vv[0].push_back(1);
vv[1].push_back(1);
vv[1].push_back(2);
vv[2].push_back(1);
vv[2].push_back(2);
vv[2].push_back(3);
// 遍历二维vector
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;
}
return 0;
}
2、经典案例:杨辉三角
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv(numRows);
// 每行大小设为i+1,全部初始化为1
for (int i = 0; i < numRows; ++i)
vv[i].resize(i + 1, 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;
}
};
class Solution {
public:
vector<int> getRow(int rowIndex) {
vector<vector<int>> vv(rowIndex+1);
// 每行大小设为i+1,全部初始化为1
for (int i = 0; i <= rowIndex; ++i)
vv[i].resize(i + 1, 1);
// 中间元素 = 上一行两个相加
for (int i = 2; i <= rowIndex; ++i)
for (int j = 1; j < i; ++j)
vv[i][j] = vv[i-1][j] + vv[i-1][j-1];
return vv[rowIndex];
}
};
总结
本文主要是讲解了vector容器的用法和新手可能遇到的坑,希望对大家有所帮助。
更多推荐
所有评论(0)