文章目录
引言
c++ STL又叫 standard template libaray 标准模板库。容器又是我们STL里面非常重要的模块。
下面列出来的是我们常用到的C++STL容器
这篇文章我们就主要来讲解一下顺序容器里面的vector、list还有deque吧~
1、引言
首先,为了更加清晰明了的了解到每个顺序容器,首先,我将他们的共同点都提取出来。
一、构造函数
系统为顺序容器提供了4种构造方式,即定义方式,以vector为例:
- 默认构造: 直接开辟空间;如vector vec
- 带有一个整型n的构造:开辟n个大小的空间,用0初始化;如
vector<int> vec(10)
,开辟10个空间,初始化为0 - 带有2个参数n,m的构造函数 :开辟n个空间,用m初始化。如
vector<int>vec(10,20)
,开辟10个空间,用20初始化。 - 传入迭代器(指针)区间的构造函数:传入起始位置,末尾位置,会把起始~末尾的迭代器区间元素传入容器。注意也可以传入数组,字符串的指针。如
vector<int> vec(arr,arr+len)
,将arr~arr+len区间的数据传入容器。
list,deque同理,都有这四种构造方式。
二、函数
格式 | 含义 |
---|---|
c.begin(); | 返回一个迭代器,它指向容器c的第一个元素 |
c.end() | 返回一个迭代器,它指向容器c的最后一个元素的下一位置,是一个无效位置 |
c.rbegin() | 返回一个逆迭代器,它指向容器c的最后的一个元素 |
c.rend() | 返回一个逆迭代器,它指向容器c的第一个元素前面的位置,是一个无效位置 |
c.size() | 返回容器c中的元素个数 |
c.max_size() | 返回容器c可容纳的最多元素个数 |
c.empty() | 返回容器大小是否为0的布尔值 |
c.clear() | 删除容器c内的所有元素。返回void |
三、遍历方式
可以通过迭代器来遍历容器中的元素。
以vector为例,list,deque同理:
vector<int> it = vec.begin();
for (; it != vec.end(); ++it)
{
cout << *it << " ";
}
2、vector(数组)
vector容器几种构造方式
首先,我们看看系统提供了几种方式,具体的使用如下:
int main()
{
std::vector<int> vec1;
std::vector<int> vec2(10);
std::vector<int> vec3(10,20);
int arr[] = { 1,2,3,4,5,6,7 };
int len = sizeof(arr) / sizeof(arr[0]);
std::vector<int> vec4(arr, arr + len);
return 0;
}
根据上面的实践,对于vector的构造函数可以总结如下:
默认构造
:就开辟一个空间就可以带有一个参数的构造
:参数代表n个大小的空间,每个空间用0来初始化带两个参数的构造
:需要n个个大小的空间,每个空间都存放元素val数组类型的构造函数
:传入数组的区间。
【补充】这个区间也可以用通过迭代器的方式传入。具体实现遍历打印如下:
std::vector<int>::iterator it = vec4.begin();
while (it != vec4.end())
{
std::cout << *it << " ";
}
2.1基本操作
1、增
尾插 push_back
:没有数据移动O(1)
【举个栗子】
std::vector<int> vec;
for (int i = 0; i < 5; i++)
{
vec1.push_back(i + 1);
}
按位置插入 insert
:通过迭代器代表插入位置,因为需要移动数据所以时间复杂度是 ==O(n) ==
【举个栗子】
std::vector<int>::iterator it = vec.begin() + 2;
vec.insert(it, 100);
以下表示insert的四种常用用法
插入格式 | 形式 | 含义 | 时间复杂度 |
---|---|---|---|
尾插 | push_back(a) | 将数据a尾插到容器中 | 尾插没有数据移动,所以时间复杂度为O(1) |
任意位置插入 | insert(index,first,last) | 将first~last区间内的元素插入index位置 | 存在数据的移动,时间复杂度为O(n) |
任意位置插入 | insert(index,val) | 将val元素插入index位置 | 存在数据的移动,时间复杂度为O(n) |
任意位置插入 | insert(index,count,val) | 在index位置插入count个val元素 | 存在数据的移动,时间复杂度为O(n) |
2、删
尾删pop_back
:末尾删除元素O(1)按位置删除erase
:删除迭代器指向的元素== O(n)==
没有头删,只有尾删和任意位置删除,
删除格式 | 形式 | 含义 | 时间复杂度 |
---|---|---|---|
尾删 | pop_back() | 将容器最后一个元素删除 | 尾删没有数据移动,所以时间复杂度为O(1) |
任意位置删除 | erase(first,last) | 删除first~last区间的元素 | 存在数据的移动,时间复杂度为O(n) |
任意位置删除 | erase(it) | 删除it位置的元素 | 存在数据的移动,时间复杂度为O(n) |
【举个栗子】
vec.erase(vec.begin(), vec.begin() + 2);
3、查
因为底层是一个数组,数组最大的特点就是通过数组下标随机访问,所以时间复杂度是== O(1)==。通过迭代器对vector容器遍历主要有以下两种方式。
方式一:
auto it1 = vec.begin();
for (; it1 != vec.end(); ++it1)
{
cout << *it1 << " ";
}
方式二:
int size = vec.size();
for (int i = 0; i < size; i++)
{
cout << vec[i] << " ";
}
4、其他操作
size()
:返回容器底部有效的数据元素empty()
:判断容器是否为空reserve(20)
:给vector预留空间 只给容器底层开辟指定大小的内存空间,并不会添加新的元素resize(20)
:容器扩容 不仅给容器底层开辟指定大小的内存空间,还会添加新的元素swap
:两个容器进行元素交换
2.2扩容机制
1、reserve和resize的区别
1、reserve主要是给vector容器预留空间,如下代码验证:
int main()
{
vector<int> vec;//默认开辟的vector,底层空间为0
vec.reserve(20);
cout << vec.empty() << endl;//输出布尔值
cout << vec.size() << endl;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
cout << vec.empty() << endl;
cout << vec.size() << endl;
return 0;
}
运行结果如下:
从上面的运行结果可以看出,reserve给vector容器预留空间20但是里面并没有数据所以empty为true。==直到有数据的添加,empty才为false。但是添加的过程并不会导致容量的增加
。所以最后还是20的容量。
2、resize就是容器扩容,如下代码验证
int main()
{
vector<int> vec;//默认开辟的vector,底层空间为0
vec.resize(20);
cout << vec.empty() << endl;//输出布尔值
cout << vec.size() << endl;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100 + 1);
}
cout << vec.empty() << endl;
cout << vec.size() << endl;
return 0;
}
运行结果如下:
从运行结果可以看到,不仅仅开辟了20个空间还在里面存放了20个为0的整形元素,再添加20个元素的时候开始扩容所以empty为false.添加的过程又增加了20个元素。所以最后输出40.
2、扩容具体流程
由此,可以总结vector容器的扩容机制
如下:
- 先开辟一定大小,
- 以倍数的形式开辟更大的空间,通常以2倍开辟
- 把旧的数据拷贝到新空间中
- 释放旧空间
- 指针指向新空间,调整总大小。
2.3对容器进行连续插入或者删除操作时迭代器失效问题
对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了
1、连续删除的情况
【举个栗子】实现把vec容器中所有的偶数全部删除
代码实现如下:
int main()
{
vector<int> vec;
auto it2 = vec.begin();
while (it2 != vec.end())
{
if (*it2 % 2 == 0)
{
it2 = vec.erase(it2