yo!这里是STL::适配器相关模拟实现

news2024/11/26 10:25:24

目录

前言

适配器介绍

deque介绍(了解)

 容器适配器与普通容器的联系

stack模拟实现

queue模拟实现

priority_queue模拟实现

介绍

实现

反向迭代器模拟实现

介绍

实现 

在list类中调用

在vector类中调用

后记


前言

        在介绍完string、vector、list类之后,对应着数据结构,应该介绍栈和队列了吧,yes!但是string、vector、list类是容器,而这里的栈和队列是属于容器适配器,既然谈到了适配器,这篇文章就把适配器的相关内容介绍一下吧,包括基本介绍、deque、stack、queue、priority_queue以及反向迭代器,难以理解的内容不多,主要通过这些内容将适配器是什么、适配器有什么用理解明白即可,而这些内容本身并没有什么复杂的东西,快往下看吧!

适配器介绍

        适配器(Adapter)是一种软件设计模式,它允许将接口不兼容的类或对象组合在一起工作。适配器模式相当于两个不相容的接口之间的中间层,它转换一个接口,以便让另一个接口能够与之兼容,从而使得两个不兼容的接口可以协同工作,即将一个类的接口转换为客户希望的另外一个接口。   ——摘抄引用

        适配器分为多种,今天我们讨论的是容器适配器,其他种后面遇到再总结,容器适配器是一种特殊类型的容器,它们提供了一种不同于标准容器的接口,但基于现有的 STL容器实现,以支持特定类型的操作。

        容器适配器可以被认为是容器的封装,它们使用已有容器的接口来实现常用的数据结构。比如栈、队列、优先队列,它们都是基于vector、deque或list等现有容器实现的。使用容器适配器可以更轻松地实现常见的数据结构,并且可以避免手动实现底层数据结构的复杂性和错误。

deque介绍(了解)

        deque(double-ended queue)是双端队列,可以在两端进行插入和删除操作,是高效的O(1)时间复杂度的操作,也可以在中间插入删除,但时间复杂度是O(N),deque可以看作是一个“数组”,但它并不是真正连续的空间,而是一段段连续的空间拼接,拼接的方法就是使用了链表的思想,连续的空间是使用了顺序表的思想,所以说,deque是与顺序表和链表对齐,而不是与栈和队列对齐。

        deque的常用操作包括push_front、push_back、pop_front、pop_back、insert、erase等(如图一),逻辑结构如图二所示。

        比如说,一小块连续空间的大小能存放3个(实际情况可以改,这里为了简化理解),先尾插1,2,3,4,再头插5,6,如下图。

 容器适配器与普通容器的联系

        说了半天适配器,又介绍了deque容器,那容器适配器与普通容器到底有啥区别,或者有啥联系,我们先看看官方文档中的栈与队列类的模板列表,如下图。

         可以发现,栈和队列的类模板列表中出现了Container的类型名,也就是在定义栈和队列时不仅需要传入数据类型,还要传入所使用的底层容器,而且可以发现,栈和队列的默认底层容器是deque,当然也可以使用vector或者list容器,这就是容器适配器与普通容器的联系。

        那为什么源码会使用deque作为栈和队列的默认底层容器呢?先来看一下deque对比vector和list的优势和劣势吧。①deque更适合头尾的插入删除,复杂度都是O(1),因为vector头尾插入时需要移动大量元素,list插入过多时需要扩容,不断向OS申请空间;②deque在中间插入删除效率并不高,而且并不适合遍历,这两点加起来就能充分说明deque很适合作为栈和队列的默认底层容器,而且可以看出栈和队列正是deque优点的结晶,这是deque应用之一,实则能应用到deque的地方并不多。

stack模拟实现

        通过数据结构课程的学习我们知道,栈可以使用数组也可以使用链表形式实现,所以这里底层容器Container可以使用vector或者list,但是官方使用了上面介绍的deque容器实现。

        stack类成员就是使用传过来的容器类定义的一个对象,无需构造函数和析构函数,因为成员变量只有一个自定义类型,构造和析构都是去调用它自己的函数,然后就是将stack所需的接口使用指定类实现即可,包括压栈、出栈、访问栈顶、以及对应const对象所对应的函数。

代码:

template <class T, class Container = deque<T>>
class Stack
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_back();
	}
	T& top()
	{
		return _con.back();
	}
	const T& top() const
	{
		return _con.back();
	}
	bool empty() const
	{
		return _con.empty();
	}
	size_t size() const
	{
		return _con.size();
	}
private:
	Container _con;
};

queue模拟实现

        队列的实现与栈一致,默认底层容器也是deque,唯一不同点就是队列的接口与栈不同,注意即可。

代码:

template <class T, class Container = deque<T>>
class Queue
{
public:
	void push(const T& x)
	{
		_con.push_back(x);
	}
	void pop()
	{
		_con.pop_front();
	}
	T& front()
	{
		return _con.front();
	}
	const T& front() const
	{
		return _con.front();
	}
	T& back()
	{
		return _con.back();
	}
	const T& back() const
	{
		return _con.back();
	}
	bool empty() const
	{
		return _con.empty();
	}
	size_t size() const
	{
		return _con.size();
	}
private:
	Container _con;
};

priority_queue模拟实现

  • 介绍

        priority_queue叫做优先队列,是队列的一种,也就说大部分实现也是与普通队列一致,核心不同点在于优先队列中的元素被赋予优先级。当出队时,优先级最高的元素先出队。在一个优先队列中,元素的顺序不仅仅取决于它们进入队列的顺序,还取决于它们的优先级。这里的优先级可以自己通过仿函数Com设置,比如整数中越大的优先级越高,或者字符串相比越大优先级越高,当然,底层容器Container也是可以自己设置,可以设置为vector、list、deque。

  • 实现

        官方设置vector作为priority_queue的底层容器,在vector的基础上又设置了堆算法将vector的元素构造成堆的结构,也可以说priority_queue就是个堆,且默认是大堆

        priority_queue的成员变量也是底层容器定义的一个对象,而且上面提到priority_queue就是个堆,所以需要构造函数将传入的元素先构造成一个堆,这里使用传迭代器构造的方式,实现过程就是在数据结构课程中学习的建队的过程,即从最后一个非叶节点开始从后向前对每个节点调用向下调整算法。值得注意的是,同时要写一个默认构造函数(无参或者全缺省的普通构造函数),否则就会报“没有默认构造函数”的错。

       入队操作(push)就是先尾插元素,再对此元素调用向上调整算法形成一个堆,出队操作(pop)就是将优先级最高的元素(即堆顶)与最后一个元素交换,将优先级最高元素pop出去,再对新堆顶元素调用向下调整算法形成一个堆,其他操作包括但不限于访问堆顶元素、判空等。

代码:

template <class T, class Container = vector<T>, class Com = less<T> >
class Priority_queue
{
public:
	Priority_queue()  
	{

	}

	template <class InputIterator>
	Priority_queue(InputIterator first, InputIterator last)
	{
		//插入数据
		while (first != last)
		{
			_con.push_back(*first);
			first++;
		}
		//建堆
		for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
		{
			adjust_down(i);
		}
	}

	void adjust_up(size_t child)
	{
		Com com;
		size_t p = (child - 1) / 2;
		while (child != 0)
		{
			//if (_con[child] > _con[p])
			//if (_con[p] < _con[child])
			if (com(_con[p], _con[child]))
			{
				std::swap(_con[child], _con[p]);
			}
			else
				break;
			child = p;
			p = (child - 1) / 2;
		}
	}
	void push(const T& x)
	{
		_con.push_back(x);
		adjust_up(_con.size() - 1);
	}

	void adjust_down(size_t parent)
	{
		Com com;
		size_t ch = parent * 2 + 1;
		while (ch < _con.size())
		{
			//if (ch < _con.size() - 1 && _con[ch + 1] > _con[ch])
			//if (ch < _con.size() - 1 && _con[ch] < _con[ch + 1])
			if (ch<_con.size() - 1 && com(_con[ch], _con[ch + 1]))
			{
				ch++;
			}
			//if (_con[ch] > _con[parent])
			//if (_con[parent] < _con[ch])
			if (com(_con[parent] , _con[ch]))
			{
				std::swap(_con[parent], _con[ch]);
			}
			else
				break;
			parent = ch;
			ch = parent * 2 + 1;
		}
	}
	void pop()
	{
		std::swap(_con.front(), _con.back());
		_con.pop_back();
		adjust_down(0);
	}

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

	T& top()
	{
		return _con.front();
	}
	const T& top() const
	{
		return _con.front();
	}
private:
	Container _con;
};

反向迭代器模拟实现

  • 介绍

        在介绍string、vector、list的迭代器过程中,无论是原生指针还是包装成一个类,我们都只了解到了普通迭代器和const迭代器,当初在了解stl的六大件时得知,迭代器不止有那些,还有反向迭代器以及反向const迭代器,之前是因为还未了解到适配器,所以将反向迭代器的介绍留在了这里。那为什么将反向迭代器留在适配器的地方讲呢?是因为反向迭代器是一个迭代器适配器,传进一个容器的迭代器,就能适配出对应的反向迭代器。

        与正向迭代器类似,反向迭代器可以倒序遍历容器中的元素,通过调用rbegin()和rend()方法获取反向迭代器的起始和结束位置,同时也可以使用*、->、++等操作符进行访问和移动。

        注意:也不是所有容器的迭代器都可以适配出反向迭代器,比如<forward_list>(单链表)、<unordered_map>、<unordered_set>,这些容器都不能逆向遍历。

  • 实现 

         根据介绍,反向迭代器也是使用类模板实现,模板参数中传入对应容器迭代器,再传入操作符*、->所需的数据类型的引用和指针形式。

        提到说反向迭代器是适配器,去调用对应容器适配器的接口以实现自己的接口,所以成员变量就是容器迭代器的类所定义的一个对象,构造函数则是根据传入的容器迭代器对象初始化反向迭代器对象。运算符++、--也是很简单,反向迭代器的++对应正向的--,反向的--对应正向的++。

        stl规定:rbegin对应end,rend对应begin,如下图

         这样的规定就导致了 解引用操作就是解引用当前迭代器的前一个迭代器,也是得到前一个迭代器的值,即先让迭代器--,再取值(如代码所示),同时->操作是取前一个迭代器的值的地址。而对于关系操作符则是比较成员对象(容器迭代器)是否相等,不做过多赘述。

代码:

template <class Iterator, class Ref, class Ptr>
class __reverse_iterator
{
public:
    //此typedef仅是简化反向迭代器的名字,无其他作用
	typedef __reverse_iterator<Iterator, Ref, Ptr> RIterator;  

	Iterator _cur;

	__reverse_iterator(Iterator x)
		:_cur(x)
	{

	}

	RIterator& operator++()
	{
		--_cur;
		return *this;
	}

	RIterator& operator--()
	{
		++_cur;
		return *this;
	}

	Ref operator*()
	{
		Iterator tmp = _cur;
		return *--tmp;
	}

	Ptr operator->()
	{
		return &(operator*());
	}

	bool operator!=(const RIterator& x)
	{
		return _cur != x._cur;
	}
};
  • 在list类中调用

        在有了list迭代器和const迭代器的基础上,传入反向迭代器类模板,形成了list的反向迭代器的类__reverse_iterator<iterator, T&, T*>,根据stl规定,rbegin对应end,rend对应begin,对应情况如下图。

         注意:对于rbegin()的返回值reverse_iterator(end()),是通过传入end()迭代器构造一个反向迭代器传回,不过不显式构造也行,直接return end()就存在隐式类型转换,也是会调用构造函数构造,其他成员函数也如此。

代码:

	//这里的iterator就是list迭代器,const_iterator就是list的const迭代器
    //typedef作用是简化类名
    typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
	typedef __reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;


	reverse_iterator rbegin()
	{
		return reverse_iterator(end());
	}
	reverse_iterator rend()
	{
		return reverse_iterator(begin());
	}
	const_reverse_iterator rbegin() const
	{
		return const_reverse_iterator(end());
	}
	const_reverse_iterator rend() const
	{
		return const_reverse_iterator(begin());
	}
  • 在vector类中调用

        调用情况与list中一致,多写一份调用就多份理解,仔细琢磨一下。

代码:

	//这里的iteratior是vector类的迭代器
    typedef __reverse_iterator<iterator, T&, T*> reverse_iterator;
	typedef __reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;


	reverse_iterator rbegin()
	{
		return reverse_iterator(end());
	}
	reverse_iterator rend()
	{
		return reverse_iterator(begin());
	}
	const_reverse_iterator rbegin() const
	{
		return const_reverse_iterator(end());
	}
	const_reverse_iterator rend() const
	{
		return const_reverse_iterator(begin());
	}

后记

         目前阶段,适配器相关内容大概就包括以上内容,完整看下来的话可以发现,适配器就是借用已有容器的接口实现自己专用的接口,比如,你有苹果13(有苹果13的充电器),有一天你又买了一部苹果14,但没有苹果14的充电线,就用13的充电器,的确也可以冲,但是冲得慢且有时接触不良,效果不好,所以你就想,有苹果13的充电器,能不能买个转接头,插在13的充电器的头上就可以给14充电了,而且充电迅速且效果好。此时,转换头就是所谓的适配器。

        这么说,应该就很好理解了,上面常见的适配器相关介绍还有不懂的可以私我也可以评论区,加油,拜拜!


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

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

相关文章

chapter 1 formation of crystal, basic concepts

chapter 1 晶体的形成 1.1 Quantum Mechanics and atomic structure 1.1.1 Old Quantum Theory problems of planetary model: atom would be unstableradiate EM wave of continuous frequency to solve the prablom of planetary model: Bohr: Quantum atomic structureP…

算符优先文法语法分析

1、实验目的及要求 1.1、实验目的 加深对语法分析器工作过程的理解&#xff1b;加强对算符优先分析法实现语法分析程序的掌握&#xff1b;能够采用一种编程语言实现简单的语法分析程序&#xff1b;能够使用自己编写的分析程序对简单的程序段进行语法翻译。 1.2、实验要求 花一…

小龟带你妙写排序之选择排序

选择排序 一. 原理二. 题目三. 思路分析四. 代码 一. 原理 选择排序(Selection-sort)是一种简单直观的排序算法。 工作原理&#xff1a;首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后&#xff0c;再从剩余未…

C语言快速回顾(三)

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。C/C是音视频必…

AtCoder Beginner Contest 314

A.直接模拟就行 #include <bits/stdc.h> using namespace std; const int N 2e510; #define int long long int n,m; string s"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679";void solve(){cin…

【电池-超级电容器混合存储系统】单机光伏电池-超级电容混合储能系统的能量管理系统(Simulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Three.js 实现材质边缘通道发光效果

相关API的使用&#xff1a; 1. EffectComposer&#xff08;渲染后处理的通用框架&#xff0c;用于将多个渲染通道&#xff08;pass&#xff09;组合在一起创建特定的视觉效果&#xff09; 2. RenderPass(是用于渲染场景的通道。它将场景和相机作为输入&#xff0c;使用Three.…

MySQL数据库----------安装anaconda---------python与数据库的链接

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

【福建事业单位-数学运算】04计算、最值和几何

【福建事业单位-数学运算】04计算、最值和几何 一、计算1.1 基础计算1.2 数列计算等差数列等比数列 总结 二、最值问题2.1 最不利构造最不利加排列组合 2.2 构造数列 三、几何问题2.1 公式计算类规则图形非规则图形 2.2结论技巧性&#xff08;三角形&#xff09;总结 一、计算 …

【Zabbix安装-5.5版本】

Zabbix安装&#xff08;rpm包安装&#xff09; Index of /zabbix/zabbix/5.5/rhel/8/x86_64/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror rpm包链接&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/zabbix/zabbix/5.5/rhel/8/x86_64/zabbix-release-5.5-1.e…

日常BUG——通过命令行创建vue项目报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 在使用vue命令行创建一个vue项目时&#xff0c;出现一下的错误&#xff1a; vue create my…

无涯教程-Perl - ref函数

描述 如果EXPR为引用,则此函数返回真值&#xff1b;如果未提供EXPR,则为$_。返回的实际值还定义了引用所引用的实体的类型。 内置类型为- REFSCALARARRAYHASHCODEGLOBLVALUEIO::Handle 如果使用bless()函数为变量设置了祝福,则将返回新的数据类型。新的数据类型通常将是一个…

homebrew安装

1.国内镜像安装 /bin/zsh -c "$(curl -fsSL https://gitee.com/huwei1024/HomebrewCN/raw/master/Homebrew.sh)"2.选中科大下载源 3.输入密码 4.排错 5.常见错误网址 添加链接描述 6.配置环境变量

texmaker-Latex,设置biber/bibtex

打开texmaker&#xff0c;【选项】–>配置texmaker–>[命令]–>bib(la)tex&#xff0c;然后在该选项里面已有的路径下改为添加biber的路径

【数据结构】树和二叉树的概念及结构

1.树概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#…

【算法基础20-单调栈】

算法原理: 用单调递增栈&#xff0c;当该元素可以入栈的时候&#xff0c;栈顶元素就是它左侧第一个比它小的元素。 以&#xff1a;3 4 2 7 5 为例&#xff0c;过程如下&#xff1a; 动态模拟过程 题目&#xff1a; 给定一个长度为 N 的整数数列&#xff0c;输出每个数左边第一…

Vue.js 生命周期详解

Vue.js 是一款流行的 JavaScript 框架&#xff0c;它采用了组件化的开发方式&#xff0c;使得前端开发更加简单和高效。在 Vue.js 的开发过程中&#xff0c;了解和理解 Vue 的生命周期非常重要。本文将详细介绍 Vue 生命周期的四个阶段&#xff1a;创建、挂载、更新和销毁。 …

C语言快速回顾(一)

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。C/C是音视频必…

vue3中使用component动态组件常见问题

一. 在vue3中使用动态组件问题警告处理 1. 代码如下 <template><div v-for"(item, index) in navItems" :key"index"><component :is"item.component" :key"item.gameId"></component></div> </te…

【Pytroch】基于支持向量机算法的数据分类预测(Excel可直接替换数据)

【Pytroch】基于支持向量机算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.数学公式3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;是一种强大的监…