【C++ STL】 deque容器深度解析,理解deque存储结构以及常用成员函数代码示例

在这里插入图片描述

deque 容器

一:基本概念和存储结构

1. deque 容器基本概念

deque(全称为"double-ended queue",双端队列),它是一种数据结构,允许在队列的两端进行插入和删除操作。

2. deque 容器存储结构

deque 内部是由多个缓存区组成,每个缓存区都是一个固定大小的数组,存储真实的数据元素。而 中控器(Control Block) 维护的是每个缓存区的地址,使得deque像一片连续的内存空间

(1)中控器(Control Block):

一个管理缓冲区的数据结构(注意:不是指针数组)。它存储了多个指针,用于跟踪缓冲区(chunk)的位置和大小,以及维护了其他必要的信息,以确保双端队列的正确操作。

中控器通常包括以下信息:

  1. 缓冲区指针(或迭代器):指向每个缓冲区的首地址,用于定位和访问数据元素。
  2. 缓冲区大小:指示每个缓冲区所能容纳的元素数量。
  3. 缓冲区数量:记录了当前双端队列中缓冲区的个数。
  4. 其他管理信息:如首尾缓冲区的索引、空闲缓冲区的索引等。
(2)deque 容器存储结构如下图所示:

deque内部结构示意图
示例:deque 插入(删除)全过程:

deque 容器进行头插(尾插)操作时,如果已有的缓存区已满,就会在头部(尾部)再开辟一段相同大小数组作为缓存区插入数据,并将新段链接到在中控器的头部(尾部)。并且中控器维护着这个缓存区的地址。

以上过程能体现出 deque容器的特点:

  1. 为什么deque看起来像是一片连续的数组。
  2. 为什么它可以在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迭代器

二: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优点:

  1. 使用emplace函数时,不需要显式地创建临时对象,可以更直接、更高效地构造新的元素。
  2. 用于在容器中直接构造元素,而不是通过拷贝或移动已存在的对象。这样做的好处是可以避免创建临时对象、减少不必要的拷贝或移动操作,从而提高性能并简化代码。这对于具有复杂构造过程或昂贵拷贝/移动构造函数的元素类型特别有益。

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的使用方法!!!
欢迎关注🔎点赞👍收藏⭐️留言📝

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值