C++_适配器模式——reverse_iterator模拟实现

news2024/11/25 2:41:26

文章目录

  • 前言
  • 适配器(Adapter)
  • 容器适配器
  • 迭代器适配器——reverse_iterator
    • 具体逻辑
    • 完整代码
  • 总结


前言

本篇博客主要会给大家讲解C++的一个代码复用的重要方式——适配器模式,并且详细讲解stl是如何运用这中设计理念来实现reverse_iterator的,给出了模拟实现方式

适配器(Adapter)

作为stl六大组件之一,适配器也是stl中重要的一部分,那么,适配器是什么呢?

适配器就是接口,对容器、迭代器、算法进行包装,但其实质还是容器、迭代器和算法,只是不依赖于具体的标准容器、迭代器和算法类型。概念源于设计模式中的适配器模式:将一个类的接口转化为另一个类的接口,使原本不兼容而不能合作的类,可以一起运作。

适配器模式的设计模式的出现,为开发带来了极大的便利,提供了一个极好的代码复用的手段,在stl中,适配器主要分为三类:容器适配器,迭代器适配器,仿函数适配器,本篇博客将简单的介绍容器适配器迭代器适配器

容器适配器

应用于容器,容器适配器包括:stack,queue,priority_queue
我们就拿stack和queue来举例,首先我们可以先看看c++文档中对这两个适配器的声明
在这里插入图片描述
在这里插入图片描述

这两个适配器容器相对于普通容器来说最大的区别就是用了第二个模板参数,而这个模板参数的默认参数是deque容器,也就是说这两个容器是在模板提供的容器的前提下进行的设计,也就是说传入什么容器就会生成底层是什么容器的栈和队列,如下:

#include<stack>
using namespace std;
int main()
{
	//默认生成用deque设立的栈
	stack<int> st1;
	//生成底层为vector的栈
	stack<int, vector<int>> st2;
	return 0;
}

那么具体是怎么实现的呢,可以看下面模拟实现的代码:

//模拟实现stl_queue

#pragma once
#include<deque>

namespace lzz
{
	template<typename T, typename container = deque<T>>
	class queue
	{
	private:
		container _con;
	public:
		queue() {}
		void push(const T& val) { _con.push_back(val); }
		void pop() { _con.pop_front(); }
		T& back() { return _con.back(); }
		const T& back() const { return _con.back(); }
		T& front() { return _con.front(); }
		const T& front() const { return _con.front(); }
		size_t size() const { return _con.size(); }
		bool empty() const { return _con.empty(); }
	};
}

///
//模拟实现stl_stack
#pragma once
#include<deque>

namespace lzz
{
	template<typename T, typename container = std::deque<T>>
	class stack
	{
	private:
		container _con;
	public:
		//自定义类型自动调用构造和析构
		stack() {}
		void push(const T& val) { _con.push_back(val); }
		void pop() {_con.pop_back(); }
		T& top() { return _con.back(); }
		size_t size() { return _con.size(); }
		bool empty() { return _con.empty(); }
	};
}

通过模拟实现,我们就可以发现其实容器适配器就是利用stl的容器接口再次进行上层封装得来的,通过上层封装能够做到忽略一些底层的细节,实现代码复用。

迭代器适配器——reverse_iterator

reverse_iterator,故名思意就是反向迭代器,大家仔细了解就能够知道,反向迭代器的++就是正向迭代器的–,反向迭代器的–就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即使用适配器模式进行设计,即:反向迭代器内部都可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。

首先,我们先来分析一下如何设计reverse_iteratorconst_reverse_iterator,需要写两份代码吗?首先我们来回顾一下iteratorconst_iterator是如何设计的:

template<typename T, typename Ref, typename Ptr>
struct __list_iterator
{
	//...
}

具体可以看下面的文章链接:
stl_list模拟实现
其中有具体的iterator的设计思路和代码

是通过传三个参数来进行控制的,所以在设计reverse_iterator时,我们也可以采取这种方式,下面是reverse_iterator的整体框架:

	template<typename Iterator, typename Ref, typename Ptr>
	class Reverse_iterator
	{
	//...
	}
	//通过如此设计反向迭代器,所有容器的反向迭代器都可以通过传入对应正向迭代器并进行typedef实现
	//list
	typedef Reverse_iterator<__list_iterator, T&, T*> reverse_iterator
	typedef Reverse_iterator<__const_list_iterator, T&, T*> const_reverse_iterator
	//
	//vector
	typedef Reverse_iterator<__vector_iterator, T&, T*> reverse_iterator
	typedef Reverse_iterator<__const_vector_iterator, T&, T*> const_reverse_iterator

具体逻辑

stl在设计反向迭代器时,采用了镜像对称的方式进行设计,也就时说,rbegin()指向的地方就是end()指向的地方,rend()指向的地方就是begin()指向的地方。

在这里插入图片描述
但是这也出现了一个问题,如果解引用,由于镜像对称的原因如果按照正常的解引用操作拿到的数据是错误的,需要拿到前一个迭代器指向的数据才能正确对应关系,所以reverse_iterator的operator*()和operator->()函数需要重新设计,如下:

		//其中先构造一个tmp的原因是不能直接修改_it,而要获得前一个数据只能先创建一个临时变量
		Ref& operator*() 
		{
			Iterator tmp(_it);
			return *--tmp;
		}
		Ptr operator->()
		{
			Iterator tmp(_it);
			--tmp;
			return &(tmp.operator*());
		}	

接着对反向迭代器进行++就是–,对正向迭代器进行–就是++,具体逻辑就是这样了。

完整代码

#pragma once

namespace lzz
{
	template<typename Iterator, typename Ref, typename Ptr>
	class Reverse_iterator
	{
	private:
		Iterator _it;
		typedef Reverse_iterator<Iterator, Ref, Ptr> self;
	public:
		Reverse_iterator() {}
		Reverse_iterator(const Iterator& it) { _it = it; }
		//由于使用了镜像对称,所以应该是返回他的上一个数据
		Ref& operator*() 
		{
			Iterator tmp(_it);
			return *--tmp;
		}
		Ptr operator->()
		{
			Iterator tmp(_it);
			--tmp;
			return &(tmp.operator*());
		}
		self& operator++()
		{
			--_it;
			return *this;
		}
		self& operator--()
		{
			++_it;
			return *this;
		}
		bool operator!=(const self& rit) { return _it != rit._it; }
	};
}

总结

通过这篇博客,相信大家对适配器模式的这种设计思路有了一定的理解,当然若要想有更深的理解,离不开不断的实践!本篇博客到此结束,如果博主有一些错误或者大家有不懂的地方欢迎大家评论区指出!

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

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

相关文章

【MySQL】centos 7下MySQL的环境搭建

从本期博客开始我们正式进入到数据库的学习&#xff0c;在学习数据库时所用到的工具是Linux环境下的MySQL 目录 一、检查环境中是否装有MySQL 二、获取MySQL官方yum源 三、配置MySQL官方yum源 四、一键安装MySQL 五、启动mysql服务 六、登录MySQL 七、修改mysql配置文件…

【Kafka】常用操作

1、基本概念 1. 消息&#xff1a; Kafka是一个分布式流处理平台&#xff0c;它通过消息进行数据的传输和存储。消息是Kafka中的基本单元&#xff0c;可以包含任意类型的数据。 2. 生产者&#xff08;Producer&#xff09;&#xff1a; 生产者负责向Kafka主题发送消息。它将消息…

智慧园区楼宇合集:数字孪生管控系统

智慧园区是指将物联网、大数据、人工智能等技术应用于传统建筑和基础设施&#xff0c;以实现对园区的全面监控、管理和服务的一种建筑形态。通过将园区内设备、设施和系统联网&#xff0c;实现数据的传输、共享和响应&#xff0c;提高园区的管理效率和运营效益&#xff0c;为居…

2023年一建学霸笔记

考点:单方取消或辞去委托承担的民事责任女《民法典》规定&#xff0c;因解除合同造成对方损失的&#xff0c;除不可归责于该当事人的事由外&#xff0c;无偿委托合同的解除方应当赔偿因解除时间不当造成的直接损失&#xff0c;有偿委托合同的解除方应当赔偿对方的直接损失和合同…

光模块高低温消光比差异大的原因分析

用于高速数字通信的光模块&#xff0c;需要具备一些特定的参数条件。其中的一个参数&#xff0c;就是消光比。消光比被用来描述最优的偏置条件和激光发射功率转化成调制功率的效率。今天就跟着小易来了解一下在实际应用中消光比产生差异的原因吧&#xff01; 一、消光比的定义…

给照片加水印软件让你保护版权不麻烦

嘿&#xff01;想要保护你的照片免受盗用吗&#xff1f;或者想为你的作品增添独特的标识&#xff1f;好消息是现在有一种水印技术可以帮你解决这些问题&#xff0c;那么&#xff0c;你知道照片加水印软件有哪些吗&#xff1f;还不清楚的朋友请你关注下这篇文章哦。接下来让我来…

125K天线驱动器芯片UM12020D 最大直流驱动电流高达1.8A

UM12020D一个集成的天线驱动器&#xff0c;该芯片提供高达1.8A的输出直流电流&#xff0c;可在0至11V的天线电源&#xff08;VM&#xff09;和1.8V至5V的器件电源电压 (VCC) 上工作。该产品具有超低的rds-on&#xff0c;采用SOP-8封装。UM12020D具有PWM&#xff08;IN1-IN2&…

防止超卖的7种实现

高并发场景在现场的日常工作中很常见&#xff0c;特别是在互联网公司中&#xff0c;这篇文章就来通过秒杀商品来模拟高并发的场景。 本文环境&#xff1a; SpringBoot 2.5.7 MySQL 8.0 X MybatisPlus Swagger2.9.2模拟工具&#xff1a; Jmeter模拟场景&#xff1a; 减库存-…

一文搞懂MQTT,如何在SpringBoot中使用MQTT实现消息的订阅和发布

之前介绍了RabbitMQ以及如何在SpringBoot项目中整合使用RabbitMQ&#xff0c;看过的朋友都说写的比较详细&#xff0c;希望再总结一下目前比较流行的MQTT。所以接下来&#xff0c;就来介绍什么MQTT&#xff1f;它在IoT中有着怎样的作用&#xff1f;如何在项目中使用MQTT&#x…

【Android】setContentView的学习笔记

启动一个Activity performLaunchActivity&#xff08;&#xff09; ActivityThread.performLaunchActivity() 方法是 Android 系统中负责启动一个 Activity 的关键方法。 当调用startActivity()方法启动一个 Activity 时&#xff0c;ActivityThread 对象会接收到该请求&…

小白必看系列之图书管理系统-登录和注册功能示例代码

文章目录 前言变量定义区域实体部分区域注册账号逻辑用户登录逻辑退出程序打印用户信息完整代码完结 前言 在现代社会中&#xff0c;计算机科学和编程技术的重要性日益凸显。作为开发者和技术爱好者&#xff0c;我们时刻追求着创新和实用性&#xff0c;希望通过技术的力量改善…

Springboot+Netty

目录 一、netty入门 二、启动方式 三、netty服务启动类 四、handler链 五、具体业务 六、 线程或者非spring管理的bean中获取spring管理的bean 七、效果 一、netty入门 Netty-导学_哔哩哔哩_bilibili 入门视频量比较大&#xff0c;最主要是了解netty的架构 netty官网&am…

Chapter 9 Port Delays (端口延迟)set input/output delay

文章目录 9.1 Input Availability---输入有效9.1.1 Min and Max Availability Time---最小和最大有效时间9.1.2 Multiple Clocks9.1.3 Understanding Input Arrival Time 9.2 Output Requirement9.2.1 Min and Max Required Time9.2.2 Multiple Reference Events9.2.3 Understa…

【梦辛工作室】IF判断优化、责任链模式 IfChain

大家好哇&#xff0c;我是梦辛工作室的灵&#xff0c;在最近的开发中&#xff0c;有许多需要判断的分支处理&#xff0c;且处理内容较多且复杂&#xff0c;代码就容易越写越复杂&#xff0c;导致后期无法继续更新跌打&#xff0c;然后基于这个环境&#xff0c;我用责任链模式写…

热备盘激活失败导致raid5阵列崩溃的服务器数据恢复案例

服务器数据恢复环境&#xff1a; 一台Linux Redhat操作系统服务器上有一组由5块硬盘组建的raid5阵列&#xff0c;包含一块热备盘。上层部署一个OA系统和Oracle数据库。 服务器故障&#xff1a; raid5阵列中的1块磁盘离线&#xff0c;硬盘离线却没有激活热备盘&#xff0c;直到…

系统集成|第四章(笔记)

目录 第四章 项目管理一般知识4.1 项目与项目管理4.1.1 项目4.1.2 项目的组织4.1.3 项目生命周期4.1.4 典型的信息系统项目的生命周期模型4.1.5 单个项目管理过程 上篇&#xff1a;第三章、系统集成专业技术 第四章 项目管理一般知识 4.1 项目与项目管理 4.1.1 项目 定义&…

SQL注入实操二

文章目录 一、sqli-lab靶场1.轮子模式总结2.Less-21a.注入点判断b.轮子测试c.获取数据库名称d.获取表信息e.获取列信息f.获取表内数据 3.Less-22a.注入点判断b.轮子测试c.获取数据库名称d.获取表信息e.获取列信息f.获取表内数据 4.Less-23a.注入点判断b.轮子测试c.获取数据库名…

如何模拟实现分布式文件存储

如何解决海量数据存不下的问题 传统做法是是在宕机存储。但随着数据变多&#xff0c;会遇到存储瓶颈 单机纵向扩展&#xff1a;内存不够加内存&#xff0c;磁盘不够家磁盘。有上限限制&#xff0c;不能无限制加下去 多机横向扩展&#xff1a;采用多台机器存储&#xff0c;一…

vue+axios实现点击取消请求功能

代码片段 <template> <el-button type"primary" click"clickExportData">导出</el-button><el-dialog title"正在导出&#xff0c;请稍等" :visible.sync"progressShow" :close-on-click-modal"false"…

sql优化:为什么通常选用根据id查询而不是根据name?

先来看一个最常见的问题,下面两个sql语句哪个效率更高一些&#xff1f; select * from user where id 1; select * from user where name 张三 在没有给name加索引的时候&#xff0c;id是有主键索引的&#xff0c;也就是聚集索引&#xff0c;这样就是一个BTree结构&#xf…