
C++专栏:C++_Yupureki的博客-CSDN博客
目录
[1. vector简介](#1. vector简介)
[1.1 什么是vector?](#1.1 什么是vector?)
[1.2 为什么选择vector?](#1.2 为什么选择vector?)
[2. vector的基本使用](#2. vector的基本使用)
[2.1 构造vector](#2.1 构造vector)
[2.2 迭代器使用](#2.2 迭代器使用)
[2.3 容量管理](#2.3 容量管理)
[2.4 元素访问](#2.4 元素访问)
[2.5 修改操作](#2.5 修改操作)
[3. 迭代器失效问题(重点)](#3. 迭代器失效问题(重点))
[3.1 导致迭代器失效的操作](#3.1 导致迭代器失效的操作)
[4. vector的模拟实现](#4. vector的模拟实现)
[4.1 基础框架](#4.1 基础框架)
[4.2 深拷贝问题](#4.2 深拷贝问题)
[4.3 完整实现关键函数](#4.3 完整实现关键函数)
[5. 性能优化建议](#5. 性能优化建议)
上一篇:从零开始的C++学习生活 6:string的入门使用-CSDN博客
前言
在前面我们学过了c++中的string,一个c++内置的处理字符串的类。不仅于此,c++还内置了很多像string的容器,如vector,list,stack和queue。
而今天我们要学习的是vector,即顺序表(数组),同样内置了各种我们所需的功能,无论是实战项目还是oj题都十分重要。
本篇博客将带你梳理一遍vector常用的功能。

1. vector简介
1.1 什么是vector?
vector
是C++标准库中的一个序列容器,它封装了动态大小数组的功能。主要特点包括:
-
动态扩容:根据需要自动调整容量
-
随机访问:支持通过下标直接访问元素,时间复杂度O(1)
-
连续存储:元素在内存中连续存放,缓存友好
-
类型安全:模板化设计,编译时类型检查
1.2 为什么选择vector?
传统数组的局限性
cpp
int arr[10]; // 固定大小,无法动态调整
arr[10] = 5; // 可能越界,危险!
传统数组存在数组越界的风险,并且不够智能。无法自由在里面插入数据,删除数据并保持元素顺序连续,即无法动态插入数据。
虽然我们学习数据结构时实现了自己的顺序表,但在题目当中难道还要自己手手搓一个吗?显然不现实
vector的优势
随时随用
vector是集成在c++中的一个容器,只需要包含头文件即可使用
cpp
#include <vector>
功能丰富
vector内置顺序表中几乎所有需要用到的功能,例如增删查改

2. vector的基本使用
2.1 构造vector
vector的构造和string基本一致
cpp
#include <vector>
using namespace std;
// 1. 默认构造 - 空vector
vector<int> v1;
// 2. 构造包含n个val的vector
vector<int> v2(5, 10); // {10, 10, 10, 10, 10}
// 3. 拷贝构造
vector<int> v3(v2); // 与v2相同
// 4. 使用迭代器范围构造
int arr[] = {1, 2, 3, 4, 5};
vector<int> v4(arr, arr + 5); // {1, 2, 3, 4, 5}
// 5. 初始化列表构造 (C++11)
vector<int> v5 = {1, 2, 3, 4, 5};
2.2 迭代器使用
vector的迭代器可以简单看作是数据类型的指针,如果是vector<int>那么迭代器是int*
cpp
void Print()
{
iterator it = begin();
while (it!=_finish)
{
cout << *it << " ";
it++;
}
cout << endl << "size:" << size() << " " << "capacity:" << capacity() << endl;
}
2.3 容量管理
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize | 改变vector的size |
reserve | 改变vector的capacity |
对于vector的reserve(设置容量)规则是:


如果 n 大于当前容量,则该函数会导致容器重新分配其存储,将其容量增加到 n (或更大)。
在所有其他情况下(即n小于或等于当前容量),函数调用不会导致重新分配,并且容量不受影响。
对于vector的resize(设置有效数据个数)规则是:

如果 n 小于当前容器大小,则内容将减少到其前 n 个 元素,删除超出的元素(并销毁它们)。
如果 n 大于当前容器大小,则通过在末尾插入所需数量的元素来扩展内容,以达到 n 的大小。如果指定了 val ,则新元素将初始化为 val 的副本,否则,它们将被值初始化。
如果 n 也大于当前容器容量,则会自动重新分配已分配的存储空间。
cpp
vector<int> v;
cout << v.size(); // 元素个数: 0
cout << v.capacity(); // 总容量: 0
cout << v.empty(); // 是否为空: true
v.reserve(100); // 预留100个元素空间
cout << v.capacity(); // 容量变为100
v.resize(10, 5); // 调整大小为10,新增元素初始化为5
cout << v.size(); // 元素个数: 10
2.4 元素访问
元素访问 | 接口说明 |
---|---|
operator[] | 数组下标访问 |
front | 数组第一个元素 |
back | 数组最后一个元素 |
data | 数组地址 |
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 下标访问
cout << v[0]; // 1 (不检查边界)
// 首尾元素访问
cout << v.front(); // 1
cout << v.back(); // 5
// 数据指针
int* data = v.data(); // 获取底层数组指针
2.5 修改操作
vector增删查改 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back(重点) | 尾删 |
find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
cpp
vector<int> v = {1, 2, 3};
// 尾部操作
v.push_back(4); // {1, 2, 3, 4}
v.pop_back(); // {1, 2, 3}
// 插入操作
v.insert(v.begin() + 1, 10); // {1, 10, 2, 3}
// 删除操作
v.erase(v.begin() + 1); // {1, 2, 3}
v.erase(v.begin(), v.begin() + 2); // {3}
// 交换
vector<int> v2 = {4, 5, 6};
v.swap(v2); // 交换两个vector的内容
find(v.begin(),v.end(),2)//algorithm库中的函数,查找对应值的数据
3. 迭代器失效问题(重点)
迭代器失效是使用vector
时最容易出错的地方,必须特别重视。
3.1 导致迭代器失效的操作
扩容操作
cpp
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.reserve(100); // 扩容,迭代器it失效!
// cout << *it; // 错误!可能崩溃或输出错误数据
扩容之后,相当于是重新开辟了一块空间,清除掉了之前的空间,而迭代器仍指向之前空间的一个地址,就成了野指针
插入操作
cpp
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.insert(v.begin(), 0); // 插入可能导致扩容,it失效!
插入元素期间,可能会引起扩容,而导致原空间被释放
删除操作
cpp
vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 1; // 指向2
v.erase(v.begin()); // 删除1,后面的元素前移
// cout << *it; // it指向的位置现在是什么?不确定!
删除1时,会使后面的数据前移,即{2,3,4}
之前的it指向第二个位置即2,但删除操作之后数据前移,第二个位置就成了3,it由原来的2变成了3,导致it指向的数据混乱,甚至数组越界
正确示例:
无论是扩容,插入还是删除,都必须保证迭代器有效
cpp
vector<int> v = {1, 2, 3, 4};
auto it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) {
it = v.erase(it); // erase返回下一个有效迭代器
} else {
++it;
}
}
4. vector的模拟实现
4.1 基础框架
在vector库中,vector的成员变量有_start(数组首地址),_finish(数组末尾元素的下一个地址),_end_of_storage(存储空间的末尾)组成
cpp
template<typename T>
class vector {
private:
T* _start; // 指向首元素
T* _finish; // 指向最后一个元素的下一个位置
T* _end_of_storage; // 指向存储空间的末尾
public:
// 构造函数
vector() : _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}
vector(size_t n, const T& val = T()) {
_start = new T[n];
_finish = _start + n;
_end_of_storage = _finish;
for (size_t i = 0; i < n; ++i) {
_start[i] = val;
}
}
// 析构函数
~vector() {
if (_start) {
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
// 容量相关
size_t size() const { return _finish - _start; }
size_t capacity() const { return _end_of_storage - _start; }
bool empty() const { return _start == _finish; }
// 元素访问
T& operator[](size_t pos) { return _start[pos]; }
const T& operator[](size_t pos) const { return _start[pos]; }
};
4.2 深拷贝问题
错误方式(使用memcp):
cpp
void reserve(size_t n) {
if (n > capacity()) {
T* tmp = new T[n];
memcpy(tmp, _start, size() * sizeof(T)); // 浅拷贝!
delete[] _start;
_start = tmp;
_finish = _start + size();
_end_of_storage = _start + n;
}
}
问题 :如果T
是管理资源的类(如string
),memcpy
会导致多个对象共享同一资源,析构时重复释放。
正确方式:
cpp
void reserve(size_t n) {
if (n > capacity()) {
T* tmp = new T[n];
// 使用placement new或循环赋值,调用拷贝构造
for (size_t i = 0; i < size(); ++i) {
tmp[i] = _start[i]; // 调用赋值运算符,深拷贝
}
delete[] _start;
_start = tmp;
_finish = _start + size();
_end_of_storage = _start + n;
}
}
4.3 完整实现关键函数
cpp
// 拷贝构造(深拷贝)
vector(const vector<T>& v)
: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {
reserve(v.capacity());
for (size_t i = 0; i < v.size(); ++i) {
_start[i] = v[i];
}
_finish = _start + v.size();
}
// 赋值运算符(现代写法)
vector<T>& operator=(vector<T> v) { // 传值,利用拷贝构造
swap(v);
return *this;
}
void swap(vector<T>& v) {
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// push_back
void push_back(const T& val) {
if (_finish == _end_of_storage) {
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
++_finish;
}
// insert
iterator insert(iterator pos, const T& val) {
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage) {
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; // 扩容后迭代器失效,需要重新计算
}
// 后移元素
for (auto it = _finish; it > pos; --it) {
*it = *(it - 1);
}
*pos = val;
++_finish;
return pos;
}
// erase
iterator erase(iterator pos) {
assert(pos >= _start && pos < _finish);
for (auto it = pos; it < _finish - 1; ++it) {
*it = *(it + 1);
}
--_finish;
return pos;
}
5. 性能优化建议
-
预分配空间 :如果知道大概的元素数量,使用
reserve()
避免多次扩容 -
使用emplace_back:C++11引入,避免不必要的拷贝/移动
-
避免在循环中判断容量:在循环前确保容量足够
-
使用swap释放内存 :
vector<T>().swap(v)
可以真正释放内存
cpp
vector<int> v;
v.reserve(1000); // 预分配空间,避免push_back时多次扩容
for (int i = 0; i < 1000; ++i) {
v.push_back(i); // 不会触发扩容
}
// 释放内存
vector<int>().swap(v); // v现在为空,且capacity为0