deque 容器
一:基本概念和存储结构
1. deque 容器基本概念
deque(全称为"double-ended queue",双端队列),它是一种数据结构,允许在队列的两端进行插入和删除操作。
2. deque 容器存储结构
deque 内部是由多个缓存区组成,每个缓存区都是一个固定大小的数组,存储真实的数据元素。而 中控器(Control Block) 维护的是每个缓存区的地址,使得deque像一片连续的内存空间。
(1)中控器(Control Block):
一个管理缓冲区的数据结构(注意:不是指针数组)。它存储了多个指针,用于跟踪缓冲区(chunk)的位置和大小,以及维护了其他必要的信息,以确保双端队列的正确操作。
中控器通常包括以下信息:
- 缓冲区指针(或迭代器):指向每个缓冲区的首地址,用于定位和访问数据元素。
- 缓冲区大小:指示每个缓冲区所能容纳的元素数量。
- 缓冲区数量:记录了当前双端队列中缓冲区的个数。
- 其他管理信息:如首尾缓冲区的索引、空闲缓冲区的索引等。
(2)deque 容器存储结构如下图所示:
示例:deque 插入(删除)全过程:
deque 容器进行头插(尾插)操作时,如果已有的缓存区已满,就会在头部(尾部)再开辟一段相同大小数组作为缓存区插入数据,并将新段链接到在中控器的头部(尾部)。并且中控器维护着这个缓存区的地址。
以上过程能体现出 deque容器的特点:
- 为什么deque看起来像是一片连续的数组。
- 为什么它可以在O(1)的时间复杂度内执行插入删除操作。
3. deque与vector的区别:
(1)插入删除操作:
vector对于头部的插入删除操作效率较低,数据量越大,效率越低。而对于deque而言,头部插入删除操作速度会比vector快很多。
(2)迭代器更复杂:
因为deque特殊的存储结构,使得它的迭代器较于一般的迭代器更为复杂,虽然deque容器的迭代器也支持随机访问,但是访问元素的速度要低于vector。根据deque存储结构就能够理解为什么效率会低于vector。
(3)动态调整大小:
deque 可以根据需要动态地调整内部数据结构的大小,无需预先分配足够的空间。而vector 容量不足时,需要分配更大的内存块,并将现有的数据复制到新内存中,会造成一定的性能开销。
综上所述:使用 deque 还是 vector 取决于具体的需求和场景。
- 如果需要频繁在两端进行插入和删除操作,可以选择 deque。
- 如果需要高效的随机访问和存储效率,可以选择 vector。
4. deque 迭代器类型和成员函数:
(1)迭代器类型:
1. 正向迭代器 (iterator):用于从头到尾顺序遍历容器中的元素,并允许修改元素的值。
2. 常量正向迭代器 (const_iterator):用于从头到尾顺序遍历容器中的元素,但不允许修改元素的值。
3. 逆向迭代器 (reverse_iterator):用于从尾到头逆序遍历容器中的元素,并允许修改元素的值。
4. 常量逆向迭代器 (const_reverse_iterator):用于从尾到头逆序遍历容器中的元素,但不允许修改元素的值。
(2)deque 容器迭代器:
- begin():返回一个指向deque第一个元素的正向迭代器。它指向双端队列的起始位置。
- end():返回一个指向deque尾部下一个位置的正向迭代器。它指向双端队列的末尾位置,而不是最后一个元素。
- rbegin():返回一个指向deque最后一个元素的逆向迭代器。它指向双端队列的最后一个元素。
- rend():返回一个指向deque头部前一个位置的逆向迭代器。它指向双端队列的起始位置。
- cbegin():返回一个指向deque第一个元素的常量正向迭代器。它指向双端队列的起始位置,但不允许修改元素的值。
- cend():返回一个指向deque尾部下一个位置的常量正向迭代器。它指向双端队列的末尾位置,但不允许修改元素的值。
- crbegin():返回一个指向deque最后一个元素的常量逆向迭代器。它指向双端队列的最后一个元素,但不允许修改元素的值。
- crend():返回一个指向deque头部前一个位置的常量逆向迭代器。它指向双端队列的起始位置,但不允许修改元素的值。
通常情况下,begin() 和 end() 返回的是正向迭代器,rbegin() 和 rend() 返回的是逆向迭代器,cbegin() 和 cend() 返回的是常量正向迭代器,crbegin() 和 crend() 返回的是常量逆向迭代器。
(3)deque 迭代器如图所示:
二:deque初始化和成员函数使用
(1)deque 初始化:
1. deque 支持的操作:
deque<int>d1; // 默认构造
deque<int>d1(d2.begin(),d2.end()); // 区间方式赋值
deque<int>d1(num, elem); // n个elem元素方式赋值
deque<int>d1(d2); // 拷贝构造
deque<int>d1 = d2; // operator= 赋值
deque<int>d2; d2.assign(d1.begin(), d1.end()); // assign区间赋值
deque<int>d1;d1.assign(num, elem); // assign num个elem方式赋值
deque<int>d1 = { 1,2,3,4,5,6 }; // 列表方式初始化
2. deque 容器读取:
void printDeque(const deque<int>& d) {
for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++) {
//*it=10 // 使用 const 使容器中的数据不能修改
cout << *it << " ";
}
cout << endl;
}
3. 代码示例:
void test01(){
deque<int>d1;
for (int i = 0; i < 10; i++){
d1.push_back(i);
}
printDeque(d1);
deque<int>d2(d1.begin(),d1.end());//区间方式赋值
printDeque(d2);
deque<int>d3(10, 10);//n个元素方式赋值
printDeque(d3);
deque<int>d4(d3);//拷贝构造
printDeque(d4);
deque<int>d5 = d3;//operator= 赋值
printDeque(d5);
deque<int>d6; //assign赋值:区间,n个元素赋值
d6.assign(d1.begin(), d1.end());
printDeque(d6);
d6.assign(10, 100);
printDeque(d6);
deque<int>d7 = { 1,2,3,4,5,6 };// 列表方式初始化
deque<int>d8{ 1,2,3,4,5,6 };
printDeque(d7);
printDeque(d8);
}
输出结果:
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
10 10 10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10 10 10
0 1 2 3 4 5 6 7 8 9
100 100 100 100 100 100 100 100 100 100
1 2 3 4 5 6
1 2 3 4 5 6
(2)deque 容器大小相关操作:
1. 成员函数:
deque.empty(); // 判断容器是否为空
deque.size(); // 获取容器元素个数
deque.resize(); //重新指定容器的长度为 num,若变长填充新位置;若变短,删除末尾超出容器长度的元素
deque.resize(num,e); //重新指定容器的长度为 num,若变长用 e 填充新位置;若变短,删除末尾超出容器长度的元素
2. 代码示例:
void test02()
{
deque<int>d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
if (d1.empty()) {
cout << "d1为空" << endl;
}
else {
cout << "d1不为空" << endl;
}
//size() 返回容器中元素的个数
int x = d1.size();
cout << "d1元素的个数为:" << x << endl;
//resize(num) 重新指定容器的长度为num,若变长填充新位置,若变短,删除末尾超出容器长度的元素
d1.resize(11);
printDeque(d1);
d1.resize(5);
printDeque(d1);
cout<<"d1新元素个数为:" << d1.size() << endl;
//resize(num,e); 重新指定容器的长度为num,若变长用e填充新位置,若变短,删除末尾超出容器长度的元素
d1.resize(11, 2);
printDeque(d1);
d1.resize(4, 10);
printDeque(d1);
}
输出结果:
0 1 2 3 4 5 6 7 8 9
d1不为空
d1元素的个数为:10
0 1 2 3 4 5 6 7 8 9 0
0 1 2 3 4
d1新元素个数为:5
0 1 2 3 4 2 2 2 2 2 2
0 1 2 3
(3)deque 插入与删除:
1. 成员函数:
push_back(); // 在容器的尾部插入一个元素。
push_front(); // 在容器的头部插入一个元素。
pop_back(); // 删除容器的尾部元素。
pop_front(); // 删除容器的尾部元素。
insert(pos,elem); // 在pos位置插入元素elem
insert(pos,n, elem); // 在pos位置插入n个elem元素
insert(pos,begin,end); // 在pos位置插入[begin,end]区间的元素
erase(begin,end); // 删除[begin,end]区间中的元素。
erase(pos); // 删除指定单个元素
clear(); // 清空容器中的数据
2. 代码示例:
void test03()
{
deque<int>d1;
for (int i = 0; i < 10; i++)
{
d1.push_back(i);
}
printDeque(d1);
//两端插入操作
d1.push_back(10); //尾插
d1.push_front(10); //头插
d1.pop_back(); //尾删
d1.pop_front(); //头删
printDeque(d1); //0 1 2 3 4 5 6 7 8 9
//指定位置插入操作
d1.insert(d1.begin(), 11); // insert(pos,elem);
printDeque(d1);//11 0 1 2 3 4 5 6 7 8 9
d1.insert(d1.begin(),2, 11); // insert(pos,n, elem);
printDeque(d1); //11 11 11 0 1 2 3 4 5 6 7 8 9
deque<int>d2;
d2.push_back(1);
d2.push_back(2);
d1.insert(d1.begin(), d2.begin(), d2.end());// insert(pos,begin, end);
printDeque(d1); //1 2 11 11 11 0 1 2 3 4 5 6 7 8 9
//删除指定单个元素操作
deque<int>::iterator it = d1.begin();
it++;//第二个元素
d1.erase(it); // erase(pos);
printDeque(d1);
//区间删除操作
deque<int>::iterator i = d1.begin();
*i++;
d1.erase(i, d1.end()); // erase(begin,end);
printDeque(d1);
//清空
d1.clear();
printDeque(d1);
}
(4)deque 中 emplace 的使用
1. emplace 与 insert 的区别:
emplace优点:
- 使用emplace函数时,不需要显式地创建临时对象,可以更直接、更高效地构造新的元素。
- 用于在容器中直接构造元素,而不是通过拷贝或移动已存在的对象。这样做的好处是可以避免创建临时对象、减少不必要的拷贝或移动操作,从而提高性能并简化代码。这对于具有复杂构造过程或昂贵拷贝/移动构造函数的元素类型特别有益。
emplace 与insert区别:
使用insert函数时,必须先创建一个临时对象,然后将该对象插入容器。这涉及到对象的拷贝或移动操作,会带来额外的开销。
适用范围:
insert函数适用于需要将已存在的对象插入容器的情况
emplace函数适用于直接在容器中构造新元素的情况。
2. 函数原型:
emplace(it,elem); // 迭代器指向的位置上插入元素elem
emplace_front(); // 容器头部插入元素
emplace_back(); // 容器尾部插入元素
3. 代码示例:
void test04()
{
deque<int>d1;
for (int i = 0; i < 5; i++)
{
d1.push_back(i);
}
//emplace()
deque<int>::iterator it = d1.begin();
*it++;
d1.emplace(d1.begin(), 10);
printDeque(d1);
d1.emplace_back(10);
printDeque(d1);
d1.emplace_front(2);
printDeque(d1);
}
输出结果:
0 10 1 2 3 4
0 10 1 2 3 4 10
2 0 10 1 2 3 4 10
(5)deque 数据存取
1. 函数原型:
at(index); // 返回索引 index 指向的元素
operator[]; // 返回索引 index 指向的元素
front(); // 返回容器第一个元素
back(); // 返回容器最后一个元素
2. 代码示例:
void test05() {
deque<int>d1;
for (int i = 0; i < 5; i++)
{
d1.push_back(i);
}
printDeque(d1);
cout << "位置2的元素为:" << d1.at(1) << endl;
cout << "位置2的元素为:" << d1[1] << endl;
cout << "第一个元素为:" << d1.front() << endl;
cout << "最后一个元素为:" << d1.back() << endl;
}
输出结果:
0 1 2 3 4
位置2的元素为:1
位置2的元素为:1
第一个元素为:0
最后一个元素为:4
(6)deque交换元素
1. 成员函数:
swap(); // 交换两个容器对象
2. 代码示例:
void test06() {
deque<int>d1;
for (int i = 0; i < 5; i++)
{
d1.push_back(i);
}
cout << "d1 = ";
printDeque(d1);
deque<int>d2;
for (int i = 10; i > 0; i--) {
d2.push_back(i);
}
cout << "d2 = ";
printDeque(d2);
d2.swap(d1);
cout << "交换后:" << endl;
cout << "d1 = ";
printDeque(d1);
cout << "d2 = ";
printDeque(d2);
}
输出结果:
d1 = 0 1 2 3 4
d2 = 10 9 8 7 6 5 4 3 2 1
交换后:
d1 = 10 9 8 7 6 5 4 3 2 1
d2 = 0 1 2 3 4
三:结语:
本文详细解析了deque的存储结构:由多个缓存区组成,中控器维护缓存区地址构成的存储结构。迭代器的四种类型:正向,常量正向,逆向,常量逆向迭代器以及成员函数的详细使用和代码展示:
本文详细说明了以下的成员函数的使用方法:
- oeprator[]
- operator=
- at()
- size()
- resize()
- push_back()
- push_front()
- pop_back()
- pop_front()
- front()
- back()
- begin()
- end()
- clear()
- insert()
- erase()
- swap()
- emplace()
- emplace_back()
- emplace_front()
- assign()
- empty()
应该熟练掌握以上的各种用法,重点在于插入删除操作,理解emplace 的关键,以便在编程过程中更加合理使用该容器。
希望本文能够让大家更好的掌握deque的使用方法!!!
欢迎关注🔎点赞👍收藏⭐️留言📝