C++ STL学习之【容器适配器】

news2024/10/7 18:26:27

✨个人主页: 夜 默
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源

  • A year from now you may wish you had started today.
    • 明年今日,你会希望此时此刻的自己已经开始行动了。

夕阳西下


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、适配器模式
    • 2、栈 stack
      • 2.1、常用接口学习
      • 2.2、模拟实现
    • 3、队列 queue
      • 3.1、常用接口学习
      • 3.2、模拟实现
    • 4、小结
    • 5、双端队列 deque(了解)
  • 🌆总结


🌇前言

适配器(配接器)是 STL 中的六大组件之一,扮演着轴承、转换器的角色,使得 STL 中组件的使用更为灵活,比如 栈和队列 就是属于适配器而非容器,以及神秘的反向迭代器也属于适配器

具有多种功能的电源适配器,可以满足多种需求

电源转换器


🏙️正文

1、适配器模式

从应用角度出发,STL 中的适配器可以分为三类:

  • 容器适配器 container adapters
  • 迭代器适配器 iterator adapters
  • 仿函数适配器 functor adapters

其中,容器适配器 可修改底层为指定容器,如由 vector 构成的栈、由 list 构成的队列;迭代器适配器可以 实现其他容器的反向迭代器(后续介绍);最后的仿函数适配器就厉害了,几乎可以 无限制的创造出各种可能的表达式

本文介绍的是容器适配器,即 队列,最后还会介绍一下常作为这两种容器适配器的默认底层容器 双端队列

适配器
出自 《STL源码剖析》


2、栈 stack

stack 是一种特殊的数据结构,主要特点为 先进后出 FILO,主要操作有:入栈、出栈、查看栈顶元素、判断栈空等;栈在原则上是不允许进行中部或头部操作的,这样会破坏栈结构的完整性

栈在生活中比较少见,比较形象的例子是米缸,最先倒进去的米总是最后才能吃到,这正好契合了栈先进后出的思想
栈

官方文档中 stack 的接口也是比较少的

栈的介绍
栈的接口
可以看出,栈有两个模板参数

  • 参数1:T 栈中的元素类型,同时也是底层容器中的元素类型
  • 参数2:Container 实现栈时用到的底层容器,这里为缺省参数,缺省结构为 双端队列 deque

如何优雅的创建一个栈对象?

#include <iostream>
#include <stack>
#include <vector>
#include <list>

using namespace std;

int main()
{
	stack<int> s;	//默认底层容器为 deque

	stack<int, vector<int>> sv;	//显示实例化底层容器为 vector
	
	stack<char, list<char>> sl;	//显示实例化底层容器为 list

	cout << typeid(s).name() << endl;	//查看具体类型
	cout << typeid(sv).name() << endl;
	cout << typeid(sl).name() << endl;

	return 0;
}

结果
注意: 关于参数3 allocator 是空间配置器,这里先不作讲解,后续再学习

2.1、常用接口学习

学习使用接口时,直接使用默认的底层容器即可(不需要传递模板参数2)

因为接口少且非常简单,所以上手起来很方便

#include <iostream>
#include <stack>

using namespace std;

int main()
{
	stack<int> s;	//构造一个栈对象

	cout << "Original:>" << endl;
	cout << "empty(): " << s.empty() << endl;
	cout << "size(): " << s.size() << endl;

	cout << endl << "Push:>" << endl;
	s.push(1);	//入栈3个元素
	s.push(2);
	s.push(3);
	cout << "empty(): " << s.empty() << endl;
	cout << "size(): " << s.size() << endl;
	cout << "top(): " << s.top() << endl;

	cout << endl << "Pop:>" << endl;
	s.pop();	//出栈两次
	s.pop();
	cout << "empty(): " << s.empty() << endl;
	cout << "size(): " << s.size() << endl;
	cout << "top(): " << s.top() << endl;

	return 0;
}

结果

栈的常用接口就这几个,多用几次就熟悉了

2.2、模拟实现

因为是容器适配器,所以在模拟实现时,需要借助其他容器,这里选择使用 vector

#pragma once
#include <vector>

using namespace std;

namespace Yohifo
{
	//这里选择模板参数2 底层容器 的缺省值为 vector
	template<class T, class Container = vector<int>>
	class stack
	{
	public:
		//需要提供一个带缺省参数的构造函数,因为默认构造函数不接受传参
		stack(const Container& c = Container())
			:_c(c)
		{}

		//不需要显式的去写析构函数,默认生成的够用了
		//同理拷贝构造、赋值重载也不需要

		bool empty() const
		{
			return _c.empty();
		}

		size_t size() const
		{
			return _c.size();
		}

		//top 需要提供两种版本
		T& top()
		{
			return _c.back();
		}

		const T& top() const
		{
			return _c.back();
		}

		//选取的底层容器必须支持尾部操作
		void push(const T& val)
		{
			_c.push_back(val);
		}

		void pop()
		{
			//空栈不能弹出,可在底层容器中检查出来
			_c.pop_back();
		}

	private:
		Container _c;	//成员变量为具体的底层容器
	};
}

适配器的厉害之处就在于 只要底层容器有我需要的函数接口,那么我就可以为其适配出一个容器适配器,比如 vector 构成的栈、list 构成的栈、deque 构成的栈,甚至是 string 也能适配出一个栈,只要符合条件,都可以作为栈的底层容器,当然不同结构的效率不同,因此库中选用的是效率较高的 deque 作为默认底层容器

图解


3、队列 queue

队列 queue 是另一种特殊的数据结构,遵循先进先出 FIFO,主要操作:入队、出队、判断队空、查看队头尾元素等

队列是一种生活中无处不见的场景,比如食堂打饭、柜台办理业务、排队出车站等等,所谓先来后到,形容的就是队列了
队列

作为容器适配器,queue 官方提供的接口也是一样的少

文档
文档
和栈一样,队列也有两个模板参数:

  • 参数1:T 队列中的元素类型,同时也是底层容器中的元素类型
  • 参数2:Container 实现队列时用到的底层容器,这里为缺省参数,缺省结构为 双端队列 deque

双端队列的优点在于高效的头尾操作和极致的空间使用,正好符合 栈和队列 的特殊需求

创建队列对象时,我们也可以指定其底层容器

#include <iostream>
#include<queue>
#include <vector>
#include <list>

using namespace std;

int main()
{
	queue<int> qDeque;	//默认使用 deque
	queue<double, vector<double>> qVector;	//指定使用 vector
	queue<char, list<char>> qList;	//指定使用 list

	cout << typeid(qDeque).name() << endl;	//查看具体类型
	cout << typeid(qVector).name() << endl;
	cout << typeid(qList).name() << endl;
	return 0;
}

结果

3.1、常用接口学习

queue 中的接口与 stack 差不多,不过 queue 出元素时是在队头操作,同时它支持访问队头、队尾元素,而 stack 只能访问栈顶元素

#include <iostream>
#include <queue>

using namespace std;

int main()
{
	queue<int> q;	//构建出一个队列对象

	cout << "Original:>" << endl;
	cout << "empty(): " << q.empty() << endl;
	cout << "size(): " << q.size() << endl;

	cout << endl << "Push:>" << endl;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	q.push(5);
	cout << "empty(): " << q.empty() << endl;
	cout << "size(): " << q.size() << endl;
	cout << "front(): " << q.front() << endl;
	cout << "back(): " << q.back() << endl;

	cout << endl << "Pop:>" << endl;
	q.pop();
	q.pop();
	q.pop();
	cout << "empty(): " << q.empty() << endl;
	cout << "size(): " << q.size() << endl;
	cout << "front(): " << q.front() << endl;
	cout << "back(): " << q.back() << endl;

	return 0;
}

结果

3.2、模拟实现

队列的模拟实现也是相当简单,这里选用 list 作为底层容器

#pragma once
#include <list>

using namespace std;

namespace Yohifo
{
	template<class T, class Container = list<T>>
	class queue
	{
	public:
		queue(const Container& c = Container())
			:_c(c)
		{}

		//这里也不需要提供拷贝构造、赋值重载、析构函数

		bool empty() const
		{
			return _c.empty();
		}

		size_t size() const
		{
			return _c.size();
		}

		//选取的底层容器中,已经准备好了相关函数,如 front、back
		T& front()
		{
			return _c.front();
		}
		const T& front() const
		{
			return _c.front();
		}

		T& back()
		{
			return _c.back();
		}
		const T& back() const
		{
			return _c.back();
		}

		void push(const T& val)
		{
			_c.push_back(val);	//队列只能尾插
		}

		void pop()
		{
			_c.pop_front();	//队列只能头删
		}

	private:
		Container _c;	//成员变量为指定的底层容器对象
	};
}

队列和栈在进行适配时,都是在调用已有的接口,若是特殊接口,比如 toppushpop 等,进行相应转换即可

  • top -> back 尾元素
  • 栈、队列 push -> push_back 尾插
  • pop -> pop_back 尾删
  • 队列 pop -> pop_front 头删

图示适配器


4、小结

栈和队列在实际开发中作为一种辅助结构被经常使用,比如内存空间划分中的栈区,设计规则符合栈 FILO;操作系统中的各种队列,如阻塞队列,设计规则符合 队列 FIFO。除此以外,在很多 OJ 题中,都需要借助栈和队列进行解题
应用场景
注:二叉树的最近公共祖先可以借助栈完成时间复杂度 O(N) 的解法

注意:

  • 栈和队列都属于特殊的数据结构,原则上是不支持遍历的,因为一旦进行遍历,其中的数据必然被弹出,因此两者都没有提供迭代器
  • 假设容器没有提供头尾操作,比如 mapset,那么就不能拿它们适配出 栈或队列,强行使用会报错

报错


5、双端队列 deque(了解)

双端队列是官方指定的底层容器,其结构上的特殊设计决定了它只适合于头尾操作需求高的场景:双端队列 = vector + list,设计时就想汲取二者的优点(下标随机访问 + 极致的空间使用),但鱼和熊掌不可兼得,在复杂结构的加持之下,双端队列趋于中庸,无法做到 vectorlist 那样极致,因此实际中也很少使用,比较适合当作适配器的底层容器

双端队列

双端队列的数据结构:list + vector

  • 利用 list 构造出一个 map 作为主控数组(通过链式结构链接),数组中元素为数组指针
  • 利用 vector 构造出大小为 N 的小数组(缓冲区),这些小数组才是双端队列存储元素的地方

注意: 此处的 map 并非是容器 map,仅仅是名字相同而已

将二者结合起来,就得到了复杂的双端队列
双端队列

deque 的扩容机制:只需要对中控数组 map 进行扩容,再将原 map 中的数组指针拷贝过来即可,效率比较高

deque 中的随机访问:

  1. (下标 - 前预留位) / 单个数组长度 获取对应小数组位置
  2. (下标 - 前预留位) % 单个数组长度 获取其在小数组中的对应下标

由此可见,单个数组大小(缓冲区大小)需要定长,否则访问时计算会比较麻烦,但长度定长后,会引发中间位置插入删除效率低的问题

对此 SGI 版的 STL 选择牺牲中间位置插入,提高下标随机访问速度,令小数组定长,这也是将它作为 栈和队列 默认底层容器的原因之一,因为 栈和队列 不需要对中间进行操作

因为中控数组是链式结构,因此双端队列的迭代器设计极为复杂

  • cur 指向当前需要的数据位置
  • first 指向 buffer 数组起始位置
  • last 指向 buffer 数组终止位置
  • node 反向指向中控数组

迭代器设计
这个迭代器还是一个随机迭代器,因此可以使用 std::sort

  • 无论是 deque 还是 list,直接排序的效率都不如借助 vector 间接排序效率高

deque 的缺点

  • 中间位置插入删除比较麻烦,可以令小数组长度不同解决问题,不过此时影响随机访问效率
  • 结构设计复杂,且不如 vectorlist 极致
  • 致命缺陷:不适合遍历,迭代器需要频繁检测是否移动某段小空间的边界,效率很低

凑巧的是,栈和队列 可以完美避开所有缺陷,全面汲取其优点,因此 双端队列 为容器适配器的默认底层容器

对于这种中庸且复杂的容器,只需要做简单了解就行了


🌆总结

以上就是本篇关于 C++ STL学习之【容器适配器】的全部内容了,在本文中,我们首先学习了适配器默认,了解它存在的意义及种类;其次学习和模拟实现了两种容器适配器 栈和队列;最后认识了容器适配器的默认底层容器 双端队列,见识了其复杂的结构设计及优缺点。关于适配的下一种形态:迭代器适配器 将在下文中学习,同时 反向迭代器 的神秘面纱也将被揭开

如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


星辰大海

相关文章推荐

STL 之 list 类

C++ STL学习之【list的模拟实现】

C++ STL学习之【list的使用】

===============

STL 之 vector 类

C++ STL学习之【vector的模拟实现】

C++ STL学习之【vector的使用】

===============

STL 之 string 类

C++ STL学习之【string类的模拟实现】

C++ STL 学习之【string】

感谢支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内存泄漏动态检测(valgrind)

初步判断是否有泄漏 Linux 内存性能优化 —— 高内存使用及内存泄漏排查 比如该文的介绍&#xff0c;需要初步了解top free -h等命令&#xff1b; 主要看free 内存泄漏检测方法&#xff1a; 静态我常用的是cppcheck&#xff1b; 动态的 Linux下内存泄漏定位方法 这个文章…

Node.js 一:基础

1.node.js是什么&#xff1a; 2.node.js的作用&#xff1a; 1.服务器应用&#xff1a;b站.. 2.开发工具类应用&#xff1a;webpack&#xff0c;bable等等 3.桌面端应用&#xff1a;vscode&#xff0c;postman等 这些应用都是基于electron框架开发&#xff0c;electron基于node…

【设计模式】23种设计模式之行为型模式

一、模板方法模式 1、豆浆制作问题 编写制作豆浆的程序&#xff0c;说明如下: 1&#xff09;、制作豆浆的流程 选材--->添加配料--->浸泡--->放到豆浆机打碎 2&#xff09;、通过添加不同的配料&#xff0c;可以制作出不同口味的豆浆 3&#xff09;、选材、浸泡和放…

拥抱还是革命,ChatGPT时代 AI专家给出15条科研生存之道

来源&#xff1a;专知 微信号&#xff1a;Quan_Zhuanzhi 你是学术机构的人工智能研究员吗?你是否担心自己无法应对当前人工智能的发展步伐?您是否觉得您没有(或非常有限)访问人工智能研究突破所需的计算和人力资源?你并不孤单; 我们有同样的感觉。越来越多的人工智能学者不…

vuepress-yarn-nodes-静态网页_个人博客搭建

nodes官网&#xff1a;https://nodejs.org/en 先下载nodes进行安装&#xff0c;一般nodes会自带包管理器npm&#xff0c;注意npm与nodes的对应关系&#xff0c;除了npm之外还有yarn包管理器&#xff0c;一般会用npm安装这个包 npm install --global yarnnpm降低版本的方法 np…

LeetCode 1026. 节点与其祖先之间的最大差值

【LetMeFly】1026.节点与其祖先之间的最大差值 力扣题目链接&#xff1a;https://leetcode.cn/problems/maximum-difference-between-node-and-ancestor/ 给定二叉树的根节点 root&#xff0c;找出存在于 不同 节点 A 和 B 之间的最大值 V&#xff0c;其中 V |A.val - B.val…

《花雕学AI》24:如何用万能Prompt公式与ChatGPT进行高效的对话测试

引言 你是否想要与人工智能进行有趣、有价值、有说服力的对话&#xff1f;你是否想要使用ChatGPT这个强大而灵活的对话生成器来创造出任何类型和主题的对话&#xff1f;如果是这样&#xff0c;那么你需要了解一个简单而强大的工具&#xff0c;就是万能Prompt公式。 万能Promp…

微软office认证课程

感谢阅读 非原创声明&#xff0c;根据微软课程学习理解而已Get started with Microsoft 365Work Smarter with Microsoft Word与 Microsoft Word 更加智能地工作Microsoft 365 SubscriptionsMicrosoft 365 订阅个人补充Exercise: Finding Help and changing account settings练…

cpp 程序与 c 程序的相互调用(extern “C“ 的使用)

实际在编译的过程中&#xff0c;.cpp 文件调用 .c文件中的函数会出错。假设代码结构如下&#xff1a; 目录 一、编译过程分析 1、预处理 2、编译 3、汇编 4、链接 二、问题解决 1、解决方案 2、解决思路 一、编译过程分析 1、预处理 该阶段头文件会被展开&#xff0c…

FPGA学习笔记(二):时序逻辑之计数器

与组合逻辑&#xff08;给定输入&#xff0c;输出是确定的&#xff0c;与时间无关&#xff09;相比较&#xff0c;时序逻辑不仅仅与输入信号相关&#xff0c;还与时钟信号相关。 D触发器&#xff1a;在上升沿时&#xff08;CLK&#xff09;才将输出&#xff08;Q&#xff09;修…

Failed to load property source from location ‘classpath:/application.yaml‘

问题 项目起动时&#xff0c;找不到yaml&#xff0c;各种处理还是没有解决&#xff0c;比如&#xff0c;clean&#xff0c;重启电脑&#xff0c;检查utf8等。 同事拉的新代码又是正常可以跑&#xff0c;这就有点奇怪了。 11:07:39.682 [main] ERROR org.springframework.boot.…

【19】linux进阶——后台运行()和nohup命令

大家好&#xff0c;这里是天亮之前ict&#xff0c;本人网络工程大三在读小学生&#xff0c;拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识&#xff0c;希望能提高自己的技术的同时&#xff0c;也可以帮助到大家 另外其它专栏请关注&#xff1a; 锐捷数通实验&…

分享自己接私活常用的开源系统

目前开源系统是JNPF框架。技术栈上是SpringBoot、SpringCloud、Spring Web、MyBatis、Swagger、Vue、Element。 这些都是比较主流的技术&#xff0c;无论是技术层面的先进性还是学习难度都是比较低的&#xff0c;目前网络上有大量可供参考学习的资料。 并且它支持前后端分离和…

Ceph入门到精通-Ceph如何扩展到超过十亿个对象?

Ceph如何扩展到超过十亿个对象&#xff1f;-ceph部署多少个节点 (51cto.com) 越来越多的组织被要求管理数十亿个文件和几百上千PB的数据。无论是在公共云还是本地环境中&#xff0c;Ceph对象存储都是值得考虑的一个选项。本篇文章将通过七部分的精选内容为下面这些问题提供答案…

vue3+echarts实现世界地图以及轨线(label使用fomatter+rich动态添加图片及背景色,以及label如何添加动态边框色)

背景 最近项目开发&#xff0c;需要开发一个世界地图&#xff0c;并且实现经纬度对应的点对点轨线动效&#xff0c;效果如下&#xff1a; 问题 如何在刚打开页面的时候就显示地点名称label如何同时添加图片和背景色label怎么动态修改字体颜色及图片以及动态边框色添加动效及…

YumRepo Error: All mirror URLs are not using ftp, http[s] or file解决办法

文章目录 一、问题背景二、问题原因三、解决方法 一、问题背景 CentOS6.5利用yum -y install gcc命令安装程序报错YumRepo Error: All mirror URLs are not using ftp, http[s] or file.Eg. Invalid release/repo/arch combination/ removing mirrorlist with no valid mirror…

maven安装教程(结合eclipse和IDEA)

一.安装maven 本文须知:安装maven环境之前要先安装java jdk环境(没有安装java环境的可以先去看安装JAVA环境的教程)Maven 3.3+ require JDK 1.7 及以上。 第一步:下载maven(本教程安装的是3.8.4) 官方下载链接:https://maven.apache.org/download.cgi Binary是可执行版本…

电子台账:生成的数据和图表导出到一个excel表中

目录 1 数据选择 1.1 选择1行数据 1.2 选择1列数据 2 图表设置 3 数据导出 为了便于进行数据分析和数据展示&#xff0c;可以把生成的汇总数据生成图表&#xff0c;然后对图表进行定制修改&#xff0c;最后把数据和图表一起导出到一个excel表中。 程序目前支持两种数据作…

【C++学习笔记】函数

值传递 值传递&#xff1a;函数调用时实参将数值传入给形参 做值传递时函数的形参发生改变&#xff0c;并不会影响实参 因为形参的作用域在函数内只有在调用函数时才会为其分配内存&#xff0c;函数调用结束后释放函数内的变量内存。 #include<iostream> using namespa…

mybatis04-mybatis缓存、分页插件、注解开发(一对一、多对一、多对多)

mybatis04 mybatis 缓存 一、mybatis 缓存概述 1、缓存 ​ 缓存 是存在于内存中的临时数据&#xff0c;使用缓存的目的是&#xff1a;减少和数据库的交互次数&#xff0c;提高执行效率。 2、mybatis 缓存 ​ mybatis 与 大多数的持久层框架一样&#xff0c;提供了缓存策略…