Vector 本质上是一个能够动态增长的数组,它在保留普通数组随机访问高效这一核心优势的同时,解决了其固定容量、不够灵活的痛点。通过自动管理内存和自动扩容的机制,它让你可以像使用无限延伸的数组一样,无需关心底层内存分配,就能高效、便捷地存储和访问一系列元素。
vector的核心特性
[] 运算符或 at() 方法,可以在 O(1) 时间复杂度内访问任何元素。
push_back)或删除(pop_back)元素,时间复杂度为 O(1)(不考虑扩容开销)。
vector是一个很奇怪的容器,它的里面没有实现流插入和流提取,所以我们就需要自己书写一个打印的函数(很简单的),话不多说,直接上代码:
void print(const vector<int>& v)
{
//下标+[]
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
//迭代器
/*vector<int>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}*/
////范围for
//支持迭代器的都支持范围for
/*for (auto e : v)
{
cout << e << " ";
}*/
}(constructor)构造函数声明 | 接口说明 |
|---|---|
vector()(重点) | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector (const vector& x); (重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
以及C++11中一个比较好用的一个构造:

ok,接下来我们一一来看:
//无参构造
vector<int> v;//创建10个数据,10个数据都是1
vector<int> v1(10, 1);//拷贝构造
vector<int> v1(10, 1);
vector<int> v2(v1);这里使用迭代器进行初始化,可以传vector类型,也可以传其他对象的迭代器(前提:类型之间可以进行转换)
1、传vector类型
//使用迭代器空间初始化
vector<int> v1(10, 1);
vector<int> v3(v1.begin(), v1.end());2、传其他类型的对象(stirng类)
//使用迭代器空间初始化
string s("hello world");
vector<int> v3(s.begin(), s.end());接下来,我们一起来看看C++11中给我们提供的一个特殊构造: 1、用法
vector<int> v4 = { 1,2,3,4,5,6,7,8,9,10 };
//更严格的写法
vector<int> v4({ 1,2,3,4,5,6,7,8,9,10 });这时候就有uu想问了,为什么可以这样写?它的底层逻辑是啥? 2、底层逻辑
在C++11中,用了 { } ,编译器就会自动认为是initializer_list,我们可以使用 { } 括任意数量的值去初始化vector对象
底层原理: 内部有两个指针,一个指针指向开头,一个指针指向结尾,可以认为是内部开了一块空间把数组存下来了。

嗯?啥意思?有点不太理解
ok,我们来看下面这张图上的内容:

我们把数组传给initializer_list中的参数il,这个参数其实是创建了一个对象,然后这个对象用指针指向这个数组的开始和结尾,内部底层就相当于写了一个范围for,然后这个范围for就遍历这个initializer_list中的参数il构造对象,然后把它给push_back到vector中
iterator的使用 | 接口说明 |
|---|---|
begin + end(重点) | 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator |

begin()是指向数据开头的迭代器,end()是指向最后一个有效数据的下一个位置
void testVector3()
{
vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });
auto it = v.begin();//指向开头数据的迭代器,也就是指向v[0]
while (it != v.end())//end()是指向最后一个数据的下一个位置的迭代器
{
cout << *it << " ";
++it;
}
}
rbegin 和 rend 正好与begin 和end相反。rbegin是指向最后一个数据的迭代器,end是指向开头数据的前一个位置的迭代器。
void testVector3()
{
vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });
auto it2 = v.rbegin();//指向最后一个数据的迭代器,也就是指向v[9]
while (it2 != v.rend())//rend()是指向开头数据的上一个位置的迭代器
{
cout << *it2 << " ";
++it2;
}
}核心接口:
容量空间 | 接口说明 |
|---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize(重点) | 改变vector的size |
reserve (重点) | 改变vector的capacity |
获取有效数据个数
void testVector3()
{
vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });
size_t size=v.size();//求出有效数据个数
cout << size << endl;
}获取空间大小
void testVector3()
{
vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });
size_t capacity = v.capacity();
cout << capacity << endl;
}判断是否为空
void testVector4()
{
vector<int> v({ 1,2,3,4,5,6,7,8,9,10 });
bool ret = v.empty();
cout << ret << endl;
if (!ret)
{
cout << "非空" << endl;
}
else
{
cout << "空的" << endl;
}
}调整 size 到 n(缺省用 0 填充)
1、开空间并插入值
void testVector4()
{
vector<int> v;
//开10个空间,没有传要插入的值,默认插入0
v.resize(10);
for (auto e : v)
{
cout << e << " ";
}
vector<int> v1;
//开10个空间,插入1
v1.resize(10, 1);
for (auto e : v1)
{
cout << e << " ";
}
}2、reszie也可以进行缩容操作,如果n<size(),就保留前n个数据
void testVector()
{
vector<int> v2({ 1,2,3,4,5,6,7,8,9,10 });
//5<v2.size() 保留前5个
v2.resize(5);
for (auto e : v2)
{
cout << e << " ";
}
}resize真正的用途是——开空间并插入值,如果对象中有数据,则在后面继续添加(情况很少)!!!
改变capacity
void testVector5()
{
vector<int> v({ 1,2,3,4,6 });
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << v.capacity() << endl;
cout << endl;
//现在我想再插入5个数据,但是空间不够了,需要扩容
//将空间扩到10
v.reserve(10);
v.push_back(5);
v.push_back(7);
v.push_back(8);
v.push_back(9);
v.push_back(10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << v.capacity() << endl;
}关键:vector 扩容策略
vector 扩容时会分配新内存、迁移旧元素、释放旧内存,这个过程耗时较高。不同编译器扩容倍数不同:

reserve的真正使用场景:提前开空间,减少扩容次数,提高效率
注意:在vector中,reserve是要多少空间就开多少空间,不会多开空间
vector增删查改 | 接口说明 |
|---|---|
push_back(重点) | 尾插 |
pop_back (重点) | 尾删 |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
operator[] (重点) | 像数组一样访问 |
当然还有 emplace 和 emplace_back等接口,后面会一一介绍~~~
在尾部插入数据
void testVector6()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
for (auto e : v)
{
cout << e << " ";
}
}删除尾部数据
void testVector6()
{
vector<int> v;
//尾插
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//尾删
v.pop_back();
v.pop_back();
for (auto e : v)
{
cout << e << " ";
}
}执行结果:

vector中没有提供头插和头删的接口,这是因为头插和头删的时间复杂度都是O(n),不建议使用头插和头删,但是可以使用insert和erase
在任意位置插入数据,insert用迭代器传位置
void testVector6()
{
vector<int> v;
//尾插
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//头插
v.insert(v.begin(), 0);
//在中间插入
v.insert(v.begin()+2, 10);
for (auto e : v)
{
cout << e << " ";
}
}执行结果:

在任意位置删除数据,erase用迭代器传位置
void testVector6()
{
vector<int> v;
//尾插
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//头删
v.erase(v.begin());
//删除中间位置数据
v.erase(v.begin()+2);
for (auto e : v)
{
cout << e << " ";
}
}执行结果:

vector中有operator[] 这个接口,我们就可以像数组一样使用下标+[ ] 的方式修改和遍历vector对象
void testVector7()
{
vector<int> v({ 1,2,3,5,6,7,8,9 });
//遍历
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
//修改下标为0的数据
v[0] = 0;
//下标+[]
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
//遍历+修改
for (size_t i = 0; i < v.size(); i++)
{
v[i]++;
cout << v[i] << " ";
}
}emplace==insert
void testVector8()
{
vector<int> v;
//尾插
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//头插
v.emplace(v.begin(), 0);
//在中间插入
v.emplace(v.begin() + 2, 10);
for (auto e : v)
{
cout << e << " ";
}
}emplace_back==push_back
但是他们两个还是有些区别的,我们一起来看一下~
ok,我们先来看一段代码:


为什么上面这段代码无法运行?这是因为AA中不支持流插入
那我们该怎么解决这个问题呢?
解决方法:

这时候就有UU想问了,为什么可以这样用? 当迭代器指向的数据是这种符合类型的类,数据是公有的,可以使用 -> ,可以认为底层的指针是数组AA的指针
ok,这时候又有UU想说:嗯?不是要说emplace_back吗?怎么说这个,这是因为emplace_back只有在这种情景下,才能说清和push_back的区别。

差异:


ok ,在现阶段中,我们就认为emplace_back和push_back 是一样的就好了~~~
https://leetcode.cn/problems/single-number/

class Solution {
public:
int singleNumber(vector<int>& nums) {
int tmp=0;
for(auto e:nums)
{
tmp^=e;
}
return tmp;
}
};https://leetcode.cn/problems/pascals-triangle/

class Solution {
public:
vector<vector<int>> generate(int numRows) {
//创建一个vector<vector<int>>对象
vector<vector<int>> vv;
//行,匿名对象初始化
vv.resize(numRows,vector<int>());
//列
for(size_t i=0;i<numRows;i++)
{
vv[i].resize(i+1,1);
}
for(size_t i=2;i<vv.size();i++)
{
for(size_t j=1;j<vv[i].size()-1;++j)
{
vv[i][j]=vv[i-1][j-1]+vv[i-1][j];
}
}
return vv;
}
};要真正的掌握这道算法题,我们就要对vector的底层有一点的了解(后面会介绍的vector的底层),我们vector其实和我们在数据结构中学的顺序表的是差不多的,既然是这样的话,那我们是不是就可以写出vector的私有成员:
class vector<int>
{
private:
int* _a;
size_t _size;
size_t _capacity;
};那我们是怎么构造出一个二维数组的呢?


用vector<vector<int>>实例化出的对象中的数组中的成员类型都是vector<int>,vector<int>实例化出的对象中的数组是一个成员类型都是int的数组。
通过上面的了解,我们就知道这个二维数组是怎么实现的了~~~

ok,那接下来我们来看看是怎么访问数据的:

上图所演示的又是怎么完成的呢?
我们先来看看这个两个 [ ] 运算符重载是啥样的: 1、

2、


对于二维数组的理解还是比较重要的!!!
ok,写到这里vector的使用基本上就已经结束了,看到这里的小伙伴给自己一个大大的👍~!!!