STL 容器:vector
vector 的技术文档:文档链接
1 vector 的概念
vector 是 STL 容器的一种,vector 既可以用来保存内置类型数据(char,short,int等),也可以用来保存自定义类型数据(对象),它是顺序表的一种实现,因此,可以将它视为一个一维数组,它还可以通过嵌套的方式来实现二维数组
想要使用 vector,就需要包含头文件 vector 并展开命名空间 std
#include <vector>
using namespace std;
接下来的说明中,主要以一维 vector (一维数组)为主
2 vector 的常用接口
2.1 vector 的构造函数
vector 的构造函数主要有以下几种:

在使用构造函数构造 vector 时,要遵循如下的语法:
vector<类型名> 对象名;
构造 vector 的方法主要有:
(1)使用空间配置器进行构造,这个方式不太常用,因此在这里不做介绍
(2)使用 n 个 val 进行构造
用这种方式构造时,val 为用户给定的值,如果未给定,则它会使用缺省值(默认值),默认值为当前 vector 存储的元素对应的类型的默认值,比如当前的 vector 要存储 int 类型的值,那么它的默认值是 0,如果要存储 string,那么它的默认值是空字符串
vector<int> v(5, 1); //n = 5, val = 1
(3)使用其它容器的迭代器来确定区间,用区间内的元素来进行构造
用这种方式构造的时候,要保证用于构造 vector 的容器中所存储的数据的类型要和 vector 中将要存储的数据的类型一致,也就是说,如果 vector 中要存储 int 类型的数据,是不可以用 string 中存储的元素来进行构造的,因为 string 中存储的是字符
string s("hello world");
vector<char> v1(s.begin(), s.end());
(4)使用一个已经存在的 vector 来进行构造(拷贝构造)
用这种方式构造的时候,要保证两个 vector 存储的数据的类型是一致的
vector<int> v(5, 1);
vector<int> v2(v);
(5)构造一个空的 vector
如果在定义 vector 时,对象名后不加括号,就会构造出一个空的 vector
vector<int> v3;
2.2 vector 的遍历
在遍历 vector 时,会使用三种方式,分别是 下标,迭代器和范围 for:
(1)使用下标遍历
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
for (size_t i = 0;i < v1.size();++i)
{
cout << v1[i] << " ";
}
return 0;
}
运行结果:

(2)使用迭代器遍历
在使用迭代器遍历时,会使用到以下几个接口:
| 接口名称 | 作用 | 返回值类型 |
|---|---|---|
| begin() | 返回指向第一个元素的迭代器 | 普通对象返回 iterator,const 对象返回 const_iterator |
| end() | 返回指向最后一个元素的下一个位置的迭代器 | 普通对象返回 iterator,const 对象返回 const_iterator |
| rbegin() | 返回指向最后一个元素的迭代器 | 普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator |
| rend() | 返回指向第一个元素的前一个位置的迭代器 | 普通对象返回 reverse_iterator,const 对象返回 const_reverse_iterator |

vector 的迭代器属于 随机迭代器,可以进行++,- -,+,- 操作,使用迭代器可以分别进行 正向遍历和反向遍历
正向遍历:
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
it++;
}
return 0;
}
反向遍历:
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int>::reverse_iterator rit = v1.rbegin();
while (rit != v1.rend())
{
cout << *rit << " ";
rit++;
}
return 0;
}
(3)范围 for 与 auto 遍历
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
for (auto e : v1)
{
cout << e << " ";
}
return 0;
}
在 auto 后加上 & 就是一个引用类型,由 auto& 修饰的变量 e 就是每一个元素的别名,此时对 e 进行修改会影响结果,但不会影响 vector 中元素的值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
for (auto& e : v1)
{
cout << e + 10 << " ";
}
return 0;
}
2.3 vector 的空间管理
在对 vector 进行空间管理时,经常会使用到以下几个接口:
| 接口名称 | 作用 | 返回值类型 |
|---|---|---|
| size() | 返回 vector 中存储的有效元素个数 | size_t (无符号整形) |
| capacity() | 返回 vector 的真实容量大小 | size_t (无符号整形) |
| resize() | 增加或缩减有效元素个数 | void |
| empty() | 判断 vector 是否为空 | bool |
| reserve() | 对 vector 预留空间 | void |

2.3.1 size()
size 的主要作用是返回当前 vector 中有效元素的个数,由于有效元素个数不可能是负值,所以返回值类型是 size_t 类型
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << v1.size() << endl;
return 0;
}
2.3.2 capacity()
capacity 的主要作用是返回当前容器的总容量, 总容量大于等于有效元素个数 size(),由于容量不可能是负值,所以返回值类型是 size_t 类型
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << v1.capacity() << endl;
return 0;
}
2.3.4 resize()

resize 的主要作用是对有效元素的个数进行缩减或增加,它的规则为:
(1)n > 总容量
先对 vector 进行扩容,扩充的容量可能等于 n,也可能大于 n,然后再增加有效元素至 n 个,用 val 来进行填充,val 具有缺省值,若 vector 存 int 类型的数据,那么它就是 0
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.resize(10);
cout << "capacity: " << v1.capacity() << endl;
cout << "size: " << v1.size() << endl;
return 0;
}
增加元素之前:
增加元素之后:
(2)总容量 > n > 当前有效元素个数
不进行扩容,增加有效元素至 n 个,用 val 来进行填充
增加元素前:
增加元素后:
(3)n < 当前有效元素个数
减少有效元素至 n 个
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "capacity: " << v1.capacity() << endl;
cout << "size: " << v1.size() << endl;
v1.resize(4);
cout << "capacity: " << v1.capacity() << endl;
cout << "size: " << v1.size() << endl;
return 0;
}
减少元素前:
减少元素后:
2.3.5 empty()
empty 的作用是判断 vector 是否为空,是空返回 true,不是空则返回 false
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int> v2;
if (v1.empty())
cout << "v1 is empty" << endl;
else
cout << "v1 is not empty" << endl;
if (v2.empty())
cout << "v2 is empty" << endl;
else
cout << "v2 is not empty" << endl;
return 0;
}
2.3.6 reserve()
reserve 的作用是为 vector 预留空间,预留空间时遵守以下的规则:
(1)n > 当前容量
进行扩容,扩充后的容量可能大于 n ,也可能等于 n
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "capacity: " << v1.capacity() << endl;
v1.reserve(10);
cout << "capacity: " << v1.capacity() << endl;
v1.reserve(20);
cout << "capacity: " << v1.capacity() << endl;
v1.reserve(30);
cout << "capacity: " << v1.capacity() << endl;
v1.reserve(40);
cout << "capacity: " << v1.capacity() << endl;
return 0;
}
(2)n <= 当前容量
不对 vector 的容量进行任何操作
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "capacity: " << v1.capacity() << endl;
v1.reserve(6); //等于
cout << "capacity: " << v1.capacity() << endl;
v1.reserve(4); //小于
cout << "capacity: " << v1.capacity() << endl;
return 0;
}
2.4 vector 的增删改查
vector 的增删改查接口主要有以下几种:
| 接口名称 | 作用 | 返回值类型 |
|---|---|---|
| operator[] () | 根据下标返回 vector 中元素 | 引用 |
| front() | 返回 vector 的第一个元素 | 引用 |
| back() | 返回 vector 的最后一个元素 | 引用 |
| push_back() | 在 vector 中尾插一个元素 | void |
| pop_back() | 在 vector 中进行尾删 | void |
| insert() | 在 vector 中插入元素 | Iterator/void |
| erase() | 在 vector 中删除元素 | Iterator |
| swap() | 交换 vector 中的所有元素 | void |
| clear() | 清除 vector 中的所有元素 | void |

2.4.1 operator[]

operator[] 的作用主要是通过下标 n 来获取 vector 中的元素,返回它的引用,返回引用的目的是为了外界能够像使用数组的下标访问一样修改 vector 中的元素,如果对象是 const 对象,那么会返回 const 引用,就意味着不能更改该对象的值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "v1[0]:" << v1[0] << endl;
cout << "v1[0]:" << v1.operator[](0) << endl;
const vector<int> v2 = { 1,2,3,4,5,6 };
cout << "v2[0]:" << v2[0] << endl;
cout << "v2[0]:" << v2.operator[](0) << endl;
return 0;
}
2.4.2 front()

front 的作用是访问 vector 的第一个元素,等同于 operator[] (0),返回的也是该元素的引用,对于 const 对象,返回的就是 const 引用,就意味着不能更改该对象的值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "v1 front:" << v1.front() << endl;
return 0;
}
2.4.3 back()

back 的作用是访问 vector 的最后一个元素,等同于 operator[] (size() - 1),返回的也是该元素的引用,对于 const 对象,返回的就是 const 引用,就意味着不能更改该对象的值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
cout << "v1 back:" << v1.back() << endl;
return 0;
}

2.4.4 push_back()
push_back 的作用是在 vector 中尾插一个元素 val 并更新 size()
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.push_back(7);
return 0;
}
2.4.5 pop_back()
pop_back 的作用是删除 vector 中的最后一个元素并更新 size()
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.pop_back();
return 0;
}
2.4.6 insert()

insert 的作用是在 vector 中的指定位置插入值并更新 size(),插入值时,如果是在中间或者最前方,则需要将后面的元素向后进行移动,时间复杂度较高,插入的方式有三种:
(1)在指定位置插入一个值
此时在插入完成后,会 返回指向新元素的迭代器
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.insert(v1.begin() + 1, 8);
return 0;
}
(2)在指定的位置插入 n 个同样的值
此时在插入完成后,不会返回任何值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.insert(v1.begin() + 1, 5, 7);
return 0;
}

(3)在指定位置插入一个区间内的值
这里的区间范围需要给出 起始迭代器和终止迭代器 来确定,并且区间是左闭右开的,插入完成后,不会返回任何值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int> v2;
v2.insert(v2.begin(), v1.begin(), v1.end()); // [begin(), end())
return 0;
}

注意事项:
在 insert 插入元素后,如果要通过迭代器来修改值,一定要接受返回值,不然会引发迭代器失效的问题,因为 insert 插入元素时,会将后面的元素整体向后移动,移动完成后,后方元素的迭代器和原来就不同了 ,比如:
此时,假设原来有个旧迭代器指向 3,但是经过了元素的移动后,现在它指向了 2,使用它进行操作是不合适的
在 insert 插入元素时,可能原来的空间已经满了,这个时候就需要开辟新空间,将原空间的元素复制到新空间中并释放旧空间,再进行插入,如果这个时候使用旧的迭代器,就会访问已经失效的旧空间,相当于野指针访问,是十分危险的行为,比如:
2.4.7 erase()

erase 的作用是删除 vector 中指定位置的元素并更新 size(),返回下一个元素的迭代器,删除的方式有两种:
(1)删除指定位置上的值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.erase(v1.begin());
return 0;
}
(2)删除指定区间内的值
在使用区间删除时,区间是左闭右开的,区间也需要起始迭代器和终止迭代器来确定
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.erase(v1.begin() + 1, v1.end() - 1); //[begin() + 1, end() - 1)
return 0;
}

注意事项:
在使用 erase 删除数据后一定要接受返回值,不然会导致迭代器失效的问题,因为删除一个元素会导致后面元素的前移,后面元素的迭代器就发生了变化,旧的迭代器无法访问到原来的值,迭代器失效,还可能引出额外的问题,比如:
在末尾是偶数时,删除 vector 里面的所有偶数可能引出越界问题
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
v1.erase(it);
it++;
}
return 0;
}

在不断删除的过程中,由于对迭代器的错误使用,导致迭代器与 vector 的末尾相互错过,引发了越界问题,正确的写法应该是:
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
it = v1.erase(it);
else
it++;
}
return 0;
}
在有连续的偶数时,删除 vector 里面的所有偶数可能会导致数据的不干净删除
int main()
{
vector<int> v1 = { 1,2,4,4,4,6 };
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
v1.erase(it);
it++;
}
return 0;
}

在不断删除的过程中,后面的元素不断前移,但是迭代器也在不断后移,就可能发生明明迭代器指向的是偶数却没有删除的情况,正确写法为:
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
it = v1.erase(it);
else
it++;
}
return 0;
}
2.4.8 swap()
swap 的作用是交换两个 vector 的值
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
vector<int> v2 = { 7,8,9,10 };
v1.swap(v2);
return 0;
}
2.4.9 clear()
clear 的作用是清除一个 vector 的值, 在清除时,不会释放 vector 的空间,只是清除了其中的元素
int main()
{
vector<int> v1 = { 1,2,3,4,5,6 };
v1.clear();
return 0;
}
3 vector 的总结
| vector | |
|---|---|
| 底层数据结构 | 动态顺序表,可不断进行扩容 |
| 查找/修改效率 | 由于支持用下标随机访问,所以查找/修改效率高 |
| 插入/删除效率 | 在尾插尾删时,由于后方没有其他元素了,所以不用移动元素,效率高。在头插/头删,中间插入删除时,需要大量移动元素,时间复杂度为O(N),效率低 |
| 空间利用率 | 由于是顺序表,空间是连续的,不会产生内存碎片,并且在处理时,会一次读入一片连续的数据,空间利用率高 |
| 迭代器类型 | 属于随机迭代器,支持++,- -,-,+ 的操作 |
| 迭代器失效 | 在插入删除时,都会出现,插入会导致后面的元素迭代器失效,删除也是一样的 |
| 应用场景 | 对插入删除要求少,需要多次进行访问/修改时 |
更多推荐






























所有评论(0)