【C++】栈和队列的模拟实现(适配器模式)

不论是C语言还是C++,我们都用其对应的传统写法对栈和队列进行了模拟实现,现在我们要用新的方法模拟实现栈和队列,这个新方法就是适配器模式

C语言传统写法: C语言模拟实现栈  

C++传统写法:C++模拟实现栈 

1.容器适配器

适配器是一种设计模式 ( 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结) 该种模式是将一个类的接口 转换 成客户希望的另外一个接口
比如说我们日常生活中用的充电器,就是一种电源适配器,本质就是对电流电压 转换成我们需要的大小。

2. stack模拟实现

stack满足只能在一端插入和删除就行,我们的底层用vector的话可以满足这个条件,底层用list同样也可以满足这个条件。

既然如此,我们就不需要原生实现栈,直接用vector或者list封装转换一下不就好了。

如果是用vector实现栈,就是数组栈,用list实现栈,就是链式栈

2.1 准备工作

我们把栈的所有实现都放在stack.h里,在test.cpp里测试。

stack.h里,包含会用到的头文件,用命名空间namespace与库里面的stack分隔开。

#pragma once
#include <iostream>
#include <list>
#include <vector>
using namespace std;namespace lyj
{}

namespace里用模板。

namespace lyj
{template<class T, class Container>class stack{private:Container _con;};
}

第一个模板参数T是栈要存的数据类型,第二个模板参数Container底层实现的类型,用Container适配转换出stack,传vector就封装vector实现,传list就封装list实现。

2.2 栈的接口实现

构造函数我们不用写,因为_con肯定是自定义结构,所以会调用自己的构造函数

我们把当作栈顶

namespace lyj
{template<class T, class Container>class stack{public:void push(const T& x) //插入{_con.push_back(x);}void pop() //删除{_con.pop_back();}const T& top() const //获取栈顶元素{return _con.back();}size_t size() const //获取有效个数{return _con.size();}bool empty() const //判空{return _con.empty();}private:Container _con;};
}

就非常方便简洁。

test.cpp使用一下我们写的这个stack。记得包含头文件#include "stack"

#include "stack.h"
void test1()
{lyj::stack<int, vector<int>> st;//注意参数st.push(1);st.push(2);st.push(3);
}
int main()
{test1();return 0;
}

上面显示就是底层是vector的栈,数组栈,然后我们插入了一些数据。

我们再演示一下底层是list的栈,链式栈,只需要把第二个参数换成list<int>就行。

void test2()
{lyj::stack<int, list<int>> st;st.push(1);st.push(2);st.push(3);
}

此时,栈的底层发生了巨大的变化,我们可以把数据存在vector实现的栈里面,也可以存在list实现的栈里面。

我们也可以给第二个参数Container给缺省值

template<class T, class Container = vector<T>>

3.queue模拟实现

queue要满足在一端插入,在另一端删除,我们的底层用list的话可以满足这个条件,但是此时vector不满足这个条件了。那我们先用list封装转换一下。

3.1 准备工作

我们把队列的所有实现都放在queue.h里,在原来的test.cpp里测试。

queue.h里用命名空间namespace与库里面的queue分隔开,这个命名空间名字和栈取一样的。

namespace lyj
{template<class T, class Container = list<T>>class queue{public:private:Container _con;};
}

第一个模板参数T是栈要存的数据类型,第二个模板参数Container底层实现的类型,这里Container缺省值给list<T>。

3.2 队列的接口实现

构造函数我们还是不用写,因为_con肯定是自定义结构,所以会调用自己的构造函数

queue的代码和stack大差不差,只是把pop部分变成_con里的头删。

template<class T, class Container = list<T>>
class queue
{
public:void push(const T& x) //队尾插入{_con.push_back(x);}void pop() //队头删除{_con.pop_front();}const T& top() const //获取队尾元素{return _con.back();}const T& front() const //获取队头元素{return _con.front();}size_t size() const //获取有效个数{return _con.size();}bool empty() const //判空{return _con.empty();}
private:Container _con;
};

test.cpp使用一下我们写的这个queue。记得包含头文件#include "squeue"

void test3()
{lyj::queue<int, list<int>> q;q.push(1);q.push(2);q.push(3);
}

 看着没啥问题。

但是我们给Container传vector类型时,这个测试代码居然也可以。

void test3()
{lyj::queue<int, vector<int>> q;q.push(1);q.push(2);q.push(3);
}

vector按理来说不能实现queue,在实现queue的pop部分,vector也没有pop_front(头删)这个接口。代码为什么没报错

这里就要补充一个知识,按需实例化。

3.3 按需实例化

我们前面的测试代码并没有调用pop这个接口,当我们调用pop这个接口时,就会立马报错。

void test3()
{lyj::queue<int, vector<int>> q;q.push(1);q.push(2);q.push(3);q.pop(); //调用pop
}

这是因为,类在实例化的时候,不会实例化所有的成员函数,我们用哪些函数,就实例化哪些

前面没报错,因为我们根本没有调用pop这个成员函数,可以理解为没有触发到这个错误。

编译器对模板检查的时候,只会检查一个大概,明显的语法错误能检查出来,但是不会检查细节,比如说我们在这里调错了函数,用到这个接口时,才会报错。

所以,在类模板实例化时,只会实例化用到的函数,这就是按需实例化。

4.deque

4.1 STL标准库中stackqueue的底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为
容器适配器,这是因为stack和queue只是 对其他容器的接口进行了包装,STL中stack和queue默认
使用deque。

4.2 deque的简单介绍

文档介绍:deque - C++ Reference

我们看deque的成员函数,会发现deque有点像vector和list的结合

vector支持[],但是vector不直接支持头删尾删。

list支持各种位置插入删除,但是不支持[]。 

 既然说到这里了,我们也顺便说一下vector和list各自的优缺点

4.2.1 vector和list各自的优缺点

vector:

优点:1.尾插尾删的效率还不错,并且支持高效的下标随机访问。

           2.物理空间连续,所以高速缓存利用率高。

缺点:1.空间需要扩容,扩容会带来效率问题和空间的浪费。

           2.头部和中间部分的插入删除操作效率低。

list:

优点:1.按需申请释放空间,不需要扩容。

           2.可以在任意位置插入删除。

缺点:不支持下标随机访问。 

4.2.2 deque的优缺点

deque的具体底层原理在这里就不详细说明了,有兴趣的可以去查阅资料。我们直接来说一下deque的优缺点。

deque:

优点:1.可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)。

           2.与vector比较,头插效率高,不需要搬移元素。

           3.与list比较,空间利用率比较高。

缺点:

        不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看 到的一个应用就是,STL用其作为stack和queue的底层数据结构

4.2.3 选deque做栈和队列的底层默认容器的原因

1. stack和queue不需要遍历(因此stackqueue没有迭代器),只需要在固定的一端或者两端进行操作

2. stack 中元素增长时, deque vector 的效率高 ( 扩容时不需要搬移大量数据 ) queue 中的元素增长时,deque 不仅效率高,而且内存使用率高。
结合了 deque 的优点,而完美的避开了其缺陷。

5. STL标准库中对于stackqueue的模拟实现

使用deque时要包含头文件#include <deque>

5.1 stack

代码和原来一样,只是缺省参数换成deque<T>

#include <iostream>
#include <list>
#include <vector>
#include <deque>
using namespace std;
namespace lyj
{template<class T, class Container = deque<T>>class stack{public:void push(const T& x) //插入{_con.push_back(x);}void pop() //删除{_con.pop_back();}const T& top() const //获取栈顶元素{return _con.back();}size_t size() const //获取有效个数{return _con.size();}bool empty() const //判空{return _con.empty();}private:Container _con;};
}

test.cpp里测试一下。

void test4()
{lyj::stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}cout << endl;
}

5.2 queue

 代码和原来一样,也是缺省参数换成deque<T>

#include <iostream>
#include <list>
#include <vector>
#include <deque>
using namespace std;
namespace lyj
{template<class T, class Container = deque<T>>class queue{public:void push(const T& x) //队尾插入{_con.push_back(x);}void pop() //队头删除{_con.pop_front();}const T& top() const //获取队尾元素{return _con.back();}const T& front() const //获取队头元素{return _con.front();}size_t size() const //获取有效个数{return _con.size();}bool empty() const //判空{return _con.empty();}private:Container _con;};
}

test.cpp里测试一下。

void test5()
{lyj::queue<int> q;q.push(1);q.push(2);q.push(3);while (!q.empty()){cout << q.front() << " ";q.pop();}cout << endl;
}

本次分享就到这里了,我们下篇再见~

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【工具变量】上市公司企业所在地城市等级直辖市、副省级城市、省会城市 计划单列市(2005-2022年)

一、包含指标&#xff1a; 股票代码 股票代码 股票简称 年份 所属城市 直辖市&#xff1a;企业所在地是否属于直辖市。1是&#xff0c;0否。 副省级城市&#xff1a;企业所在地是否属于副省级城市。1是&#xff0c;0否。 省会城市&a…

【HarmonyOS】鸿蒙应用地理位置获取,地理名称获取

【HarmonyOS】鸿蒙应用地理位置获取&#xff0c;地理名称获取 一、前言 首先要理解地理专有名词&#xff0c;当我们从系统获取地理位置&#xff0c;一般会拿到地理坐标&#xff0c;是一串数字&#xff0c;并不是地理位置名称。例如 116.2305&#xff0c;33.568。 这些数字坐…

【优选算法篇】寻找隐藏的宝藏:用二分查找打开算法世界的大门(上篇)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

【后端面试总结】golang channel深入理解

在Go语言中&#xff0c;Channel是一种用于在goroutine之间进行通信和同步的重要机制。它提供了一种安全、类型安全的方式来传递数据&#xff0c;使得并发编程变得更加直观和简单。本文将详细介绍Golang中Channel的基本概念、创建与关闭、发送与接收操作&#xff0c;以及相关的使…

centos 手动安装libcurl4-openssl-dev库

下载源代码 curl downloadshttps://curl.se/download/ 选择需要下载的版本&#xff0c;我下载的是8.11.0 解压 tar -zxvf curl-8.11.0 查看安装命令 查找INSTALL.md&#xff0c;一般在docs文件夹下 –prefix &#xff1a;指定安装路径&#xff08;默认安装在/usr/local&…

基于TensorFlow框架的线性回归实现

目录 ​编辑 线性回归简介 TensorFlow简介 线性回归模型的TensorFlow实现 1. 安装TensorFlow 2. 导入必要的库 3. 准备数据 4. 定义模型 5. 定义损失函数 6. 定义优化器 7. 训练模型 8. 评估模型 9. 模型参数的可视化 10. 模型预测的准确性评估 结论 在统计学和…

40分钟学 Go 语言高并发:服务性能调优实战

服务性能调优实战 一、性能优化实战概述 优化阶段主要内容关键指标重要程度瓶颈定位收集性能指标&#xff0c;确定瓶颈位置CPU、内存、延迟、吞吐量⭐⭐⭐⭐⭐代码优化优化算法、并发、内存使用代码执行时间、内存分配⭐⭐⭐⭐⭐系统调优调整系统参数、资源配置系统资源利用率…

云计算vsphere 服务器上添加主机配置

这里是esxi 主机 先把主机打开 然后 先开启dns 再开启 vcenter 把每台设备桌面再vmware workstation 上显示 同上也是一样 &#xff0c;因为在esxi 主机的界面可能有些东西不好操作 我们选择主机和集群 左边显示172.16.100.200

使用PaddlePaddle实现线性回归模型

目录 ​编辑 引言 PaddlePaddle简介 线性回归模型的构建 1. 准备数据 2. 定义模型 3. 准备数据加载器 4. 定义损失函数和优化器 5. 训练模型 6. 评估模型 7. 预测 结论 引言 线性回归是统计学和机器学习中一个经典的算法&#xff0c;用于预测一个因变量&#xff0…

将word里自带公式编辑器编辑的公式转换成用mathtype编辑的格式

文章目录 将word里自带公式编辑器编辑的公式转换成用mathtype编辑的格式MathType安装问题MathType30天试用延期MathPage.wll文件找不到问题 将word里自带公式编辑器编辑的公式转换成用mathtype编辑的格式 word自带公式编辑器编辑的公式格式&#xff1a; MathType编辑的格式&a…

一文说清:Git创建仓库的方法

0 引言 本文介绍如何创建一个 Git 本地仓库&#xff0c;以及与远程仓库的关联。 1 初始化仓库&#xff08;git init&#xff09; 1.1 概述 Git 使用 git init 命令来初始化一个 Git 仓库&#xff0c;Git 的很多命令都需要在 Git 的仓库中运行&#xff0c;所以 git init 是使…

【Linux系统编程】——理解冯诺依曼体系结构

文章目录 冯诺依曼体系结构硬件当代计算机是性价比的产物冯诺依曼的存储冯诺依曼的数据流动步骤冯诺依曼结构总结 冯诺依曼体系结构硬件 下面是整个冯诺依曼体系结构 冯诺依曼结构&#xff08;Von Neumann Architecture&#xff09;是现代计算机的基本结构之一&#xff0c;由数…

一、docker简介

一、docker简介 1.1 docker的前世今生 Docker是基于Go语言实现的开源容器项目&#xff0c;诞生于2013年年初&#xff0c;最初的发起者是dotCloud公司&#xff0c;Docker自开源后受到广泛的关注和讨论&#xff0c;目前已有多个相关项目&#xff08;包括Docker三剑客、Kubernet…

实验三:Mybatis-动态 SQL

目录&#xff1a; 一 、实验目的&#xff1a; 通过 mybatis 提供的各种标签方法实现动态拼接 sql 二 、预习要求&#xff1a; 预习 if、choose、 when、where 等标签的用法 三、实验内容&#xff1a; 根据性别和名字查询用户使用 if 标签改造 UserMapper.xml使用 where 标签进行…

解决Tomcat运行时错误:“Address localhost:1099 is already in use”

目录 背景: 过程&#xff1a; 报错的原因&#xff1a; 解决的方法&#xff1a; 总结&#xff1a; 直接结束Java.exe进程&#xff1a; 使用neststat -aon | findstr 1099 命令&#xff1a; 选择建议&#xff1a; 背景: 准备运行Tomcat服务器调试项目时&#xff0c;程序下…

剖析千益畅行,共享旅游-卡,合规运营与技术赋能双驱下的旅游新篇

在数字化浪潮席卷各行各业的当下&#xff0c;旅游产业与共享经济模式深度融合&#xff0c;催生出旅游卡这类新兴产品。然而&#xff0c;市场乱象丛生&#xff0c;诸多打着 “共享” 幌子的旅游卡弊病百出&#xff0c;让从业者与消费者都深陷困扰。今天&#xff0c;咱们聚焦技术…

三步入门Log4J 的使用

本篇基于Maven 的Project项目&#xff0c; 快速演示Log4j 的导入和演示。 第一步&#xff1a; 导入Log4j依赖 <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.24.2</version&…

node.js基础学习-express框架-静态资源中间件express.static(十一)

前言 在 Node.js 应用中&#xff0c;静态资源是指那些不需要服务器动态处理&#xff0c;直接发送给客户端的文件。常见的静态资源包括 HTML 文件、CSS 样式表、JavaScript 脚本、图片&#xff08;如 JPEG、PNG 等&#xff09;、字体文件和音频、视频文件等。这些文件在服务器端…

Marvell第四季度营收预计超预期,定制芯片需求激增

芯片制造商Marvell Technology&#xff08;美满电子科技&#xff09;&#xff08;MRVL&#xff09;在周二发布了强劲的业绩预告&#xff0c;预计第四季度的营收将超过市场预期&#xff0c;得益于企业对其定制人工智能芯片的需求激增。随着人工智能技术的快速发展&#xff0c;特…

主持人婚礼司仪知识点题库300道;大型免费题库;大风车题库

无偿分享&#xff0c;直接下载 原文件链接&#xff1a;大风车题库-文件 大风车题库网站&#xff1a;大风车题库