【C++进阶之路】适配器、反向迭代器、仿函数

news2025/1/13 16:51:00

文章目录

  • 前言
  • 一、适配器
    • ①模拟实现栈
    • ②模拟实现对列
  • 二、反向迭代器
  • 三、仿函数
  • 总结

前言

我们先来笼统的介绍一下今天的三个内容。

  • 适配器——简单的理解就是复用,用已经实现的轮子,来继续实现某种功能。

  • 反向迭代器——原理很简单,就是对称+复用(已经造好的正向迭代器)

  • 仿函数——与函数用法相同的类,用于排序,比如大堆,小堆,升序,降序。

一、适配器

适配器的功能,在我们模拟实现栈和对列时十分方便!
先用这两个例子感受一下。

①模拟实现栈

	template<class T,class Con = deque<T>>
	class stack
	{
	public:
	
		void push(const T& val)
		{
			st.push_back(val);
		}
		void pop()
		{
			st.pop_back();
		}
		T& top()
		{
			return st[st.size() - 1];
		}
		size_t size()
		{
			return st.size();
		}
		bool empty()
		{
			return st.empty();
		}
	private:
		Con st;
	};

②模拟实现对列

	template<class T, class Con = deque<T>>
	class queue
	{
	public:
		void push(const T& val)
		{
			qu.push_back(val);
		}
		void pop()
		{
			qu.pop_front();
		}
		T& front()
		{
			return qu[0];
		}
		size_t size()
		{
			return qu.size();
		}
		bool empty()
		{
			return qu.empty();
		}
	private:
		Con qu;
	};

这里的模板参数:

template<class T, class Con = deque<T>>
				//这就是适配器
				

看完这两个例子,你可能会明白,并且可能会惊讶于适配器的方便之处。

并且这里也给了模板一个用法——缺省参数

且如果是全缺省的话,我们示例化还是要给参数列表的。

如下:

stack<> st;

再来谈谈deque,你可能会好奇,这里在栈的第二个缺省参数为啥不给vector<T>, 对列的第二个缺省参数为啥不给list<T>

要弄清楚原因我们先得了解deque的基本结构:

在这里插入图片描述

我们知道这个中控数组一旦满了,就要考虑扩容的问题,那该如何扩呢?

只需要将中控数组进行扩容,然后将指针拷贝过去即可。

因此相较于vector无需动数据即可完成扩容!—— 提高了扩容的效率

相较于vector,这种结构也支持随机访问,不过得通过计算,这样高频率的随机访问效率会降低,缓冲命中率也有一定的下降。

相较于list,由于list是单个申请单个释放,因此deque的缓冲命中率较高。

但是相较于list,如果deque要在中间插入数据,那效率就会降低,这一点不如list。

这样:

  1. stack结构,实现尾插尾删功能,如果要考虑扩容的效率,deque的优势更大。
  2. queue结构,实现尾插头删功能,如果要考虑到缓冲命中率,deque的优势更大。

因此库里采用了deque来实现栈和对列!

二、反向迭代器

我们先来看库里的实现方式:

  • vector
    在这里插入图片描述
  • list
    在这里插入图片描述

可以看到,这里呈现对称的方式,但是如何解引用是关键

库里是这样实现的:

Reverse_iterator operator* ()
{
	Iterator tmp(_it);
	return *--tmp;
}

这里使用了正向迭代器的拷贝构造,然后再减减访问反向迭代器的下一个元素,这样实现了错位,也能刚好利用实现好的正向迭代器。

剩余的就不难实现了:

	template<class Iterator, class Ref, class Ptr>
	struct Reverse_iterator
	{
		typedef Reverse_iterator<Iterator, Ref, Ptr> self;
		//自定义的类型不能被当做类名进行使用
		
	
		Reverse_iterator(const Iterator& it)
		{
			_it = it;
		}
		
		self& operator ++()
		{
			_it--;
			return *this;
		}
		self operator ++(int)
		{
			self tmp(_it);
			_it--;
			return self;
		}
		Ref operator* ()
		{
			Iterator tmp(_it);
	
			return *--tmp;
		}
		Ptr operator->()
		{
			return _it.operator->();
		}
		bool operator!=(const Reverse_iterator&it)const
		{
			return _it != it._it;
	
		}
		bool operator==(const Reverse_iterator& it)const
		{
			return _it == it._it;
		}
		Iterator _it;
	};

然后我们只需在容器里加上下面的代码即可完成复用!

		typedef list_iterator<T,T&,T*> iterator;
		typedef list_iterator<T,const T&,const T*> const_iterator;

		typedef Reverse_iterator<iterator, T, T*> reverse_iterator;
		typedef Reverse_iterator <const_iterator, const T, const T*>\
		 const_reverse_iterator;
		 
		reverse_iterator rbegin()
		{
			return end();
			//这里会调用拷贝构造
		}
		reverse_iterator rend()
		{
			return begin();
		}

三、仿函数

我们这里先实现一个优先级对列——大堆。

	template<class T,class Container = vector<T>>
	class priority_queue
	{
		//建大堆
		void AdjustDown(int parent)
		{
			//假设左孩子较大
			size_t child = parent * 2 + 1;
			//因为是往下比较,所以child应在范围内
			while (child < con.size())
			{
				//先选出来较大的孩子,当然前提得存在。
				if (child + 1 < con.size() && con[child] < con[child + 1])
				{
					child = child + 1;
				}
				//再跟父节点进行比较,如果孩子大就换
				if (con[parent]< con[child])
				{
					swap(con[parent], con[child]);
					//因为是向下比较的,所以要看parent是不是还比下面的孩子大。
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		//建大堆
		void AdjustUp(int child)
		{
			int parent = (child - 1) / 2;
			while (parent >= 0)
			{
				if (con[parent] < con[child])
				{
					swap(con[parent], con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
	public:

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			InputIterator it = first;
			while (it != last)
			{
				con.push_back(*it);
				it++;
			}
			//进行调堆
			//从最后一个叶子结点的父节点开始。

			//时间复杂度O(N)
			for (int i = ((con.size() - 1) - 1) / 2; i >= 0; i--)
			{
				AdjustDown(i);
			}
		}
		void pop()
		{
			swap(con[0], con[con.size() - 1]);
			con.pop_back();
			AdjustDown(0);
		}
		void push(const T& val)
		{
			con.push_back(val);
			AdjustUp(con.size() - 1);
		}
		size_t size()
		{
			return con.size();
		}
		bool empty()
		{
			return con.empty();
		}
		T top()const
		{
			return con[0];
		}
		
		priority_queue()
		{}
	private:
		Container con;
	};

这里的大堆算是实现了,借助这二叉树的一点点知识。

  • 那如果我们还要实现小堆呢?

用不用cv一份,再改呢?其实不用,只需要写一个仿函数即可。

	template<class T>
	struct Less
	{
		bool operator()(const T &x1 ,const T& x2)
		{
			return x1 < x2;
		}
	};
	template<class T>
	struct Greater
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 > x2;
		}
	};

如何使用呢?用类模板+重载函数的掉用。

template<class T,class Container = vector<T>,class Compare = Less<T>>
class priority_queue
{
	//建大堆
	void AdjustDown(int parent)
	{
		//假设左孩子较大
		size_t child = parent * 2 + 1;
		//因为是往下比较,所以child应在范围内
		while (child < con.size())
		{
			//先选出来较大的孩子,当然前提得存在。
			if (child + 1 < con.size() && com(con[child] , con[child + 1]))
			{
				child = child + 1;
			}
			//再跟父节点进行比较,如果孩子大就换
			if (com(con[parent], con[child]))
			{
				swap(con[parent], con[child]);
				//因为是向下比较的,所以要看parent是不是还比下面的孩子大。
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}
	//建大堆
	void AdjustUp(int child)
	{
		int parent = (child - 1) / 2;
		while (parent >= 0)
		{
			if (com(con[parent] , con[child]))
			{
				std::swap(con[parent], con[child]);
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;
			}
		}
	}
public:

	template<class InputIterator>
	priority_queue(InputIterator first, InputIterator last)
	{
		InputIterator it = first;
		while (it != last)
		{
			con.push_back(*it);
			it++;
		}
		//进行调堆
		//从最后一个叶子结点的父节点开始。

		//时间复杂度O(N)
		for (int i = ((con.size() - 1) - 1) / 2; i >= 0; i--)
		{
			AdjustDown(i);
		}
	}
	void pop()
	{
		swap(con[0], con[con.size() - 1]);
		con.pop_back();
		AdjustDown(0);
	}
	void push(const T& val)
	{
		con.push_back(val);
		AdjustUp(con.size() - 1);
	}
	size_t size()
	{
		return con.size();
	}
	bool empty()
	{
		return con.empty();
	}
	T top()const
	{
		return con[0];
	}
	priority_queue()
	{}
private:
	Container con;
	Compare com;
};

这样既可以当做大堆使用,也可以当做小堆使用。

总结

 今天的分享就到这里了,如果觉得文章不错,点个赞鼓励一下吧!我们下篇文章再见

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

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

相关文章

Openlayers实战:setCenter,setZoom设置跳转

Openlayers开发的项目中,经常会重新设定一个zoom,也会重新跳转到一个中心点。 所用的方法就是setZoom和setCenter。在Openlayers实战中,我们做一个简单的设置,来很好的认识一下这个常用的方法。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特…

Vue3组合式API+TypeScript写法入门

文章目录 前言1.reactive2.ref3.props4.computed5.emit6.watch总结 前言 参考Vue3官网. 本篇以组合式API为例, 但不包含setup语法糖式写法. 原本打算结合class-component, Vue3不推荐就不用了: OverView|Vue Class Component. 而且是不再推荐基于类的组件写法, 推荐单文件组件…

电脑硬盘指的是什么?电脑硬盘长什么样子呢

在很早之前就听说过电脑里面有硬盘&#xff0c;但是不知道电脑硬盘是什么样子&#xff0c;本章文章结合硬盘的接口类型&#xff0c;以及应用技术&#xff0c;说说与硬盘样式有关的知识 一。机械硬盘 如果从硬盘的应用技术来区分硬盘&#xff0c;一般分为两种&#xff0c;早些年…

Leetcode周赛 | 2023-7-23

2023-7-23 题1体会我的代码 题2我的代码 题3体会我的代码 题1 体会 01背包啊。01背包啊&#xff01;怎么能一直往回溯上想&#xff01;还是对动态规划太不熟悉了&#xff01;这不就是01背包吗&#xff1f;还要别人提示才知道。 我的代码 class Solution:def numberOfWays(se…

设计模式再探——状态模式

目录 一、背景介绍二、思路&方案三、过程1.状态模式简介2.状态模式的类图3.状态模式代码4.状态模式还可以优化的地方5.状态模式的项目实战&#xff0c;优化后 四、总结五、升华 一、背景介绍 最近产品中有这样的业务需求&#xff0c;不同时间(这里不是活动的执行时间&…

Spring 统一登录验证、数据格式返回、异常处理的实现

文章目录 spring统一功能实现前言1. 统一用户登录权限验证1.1 传统实现方式1.2 Spring AOP用户统一登录验证1.2.1 Spring 拦截器 2. 统一数据格式返回3. 统一异常处理 spring统一功能实现 前言 在上一篇博客我们介绍了Spring AOP以及简单使用了Spring AOP&#xff0c;这篇博客…

力扣 -- 122. 买卖股票的最佳时机 II

一、题目&#xff1a; 题目链接&#xff1a;122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 二、解题步骤 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 三、参考代码&#xff1a; clas…

【数据可视化】基于Python和Echarts的中国经济发展与人口变化可视化大屏

1.题目要求 本次课程设计要求使用Python和ECharts实现数据可视化大屏。要求每个人的数据集不同&#xff0c;用ECharts制作Dashboard&#xff08;总共至少4图&#xff09;&#xff0c;要求输入查询项&#xff08;地点和时间&#xff09;可查询数据&#xff0c;查询的数据的地理…

工业的相机与镜头(简单选型)

面阵相机&#xff0c;需要多大的分辨率&#xff1f;多少帧数&#xff1f; 前提条件&#xff1a; 1.被检测的物体大小 2.要求检测的精度是多少 3.物体是否在运动过程中进行检测&#xff0c;速度是多少 线阵相机选择(分辨率、扫描行数) 行频&#xff1a;每秒扫描多少行&#xf…

【GitOps系列】使用Kustomize和Helm定义应用配置

文章目录 使用 Kustomize 定义应用改造示例应用1.创建基准和多环境目录2.环境差异分析3.为 Base 目录创建通用 Manifest4.为开发环境目录创建差异 Manifest5.为预发布环境创建差异 Manifest6.为生产环境创建差异 Manifest 部署 Kustomize 应用部署到开发环境部署到生产环境 使用…

Python 逻辑回归:理论与实践

文章目录 1. 介绍1.1 什么是逻辑回归&#xff1f;1.2 逻辑回归的应用领域 2. 逻辑回归的原理2.1 Sigmoid 函数2.2 决策边界2.3 损失函数 3. 逻辑回归的实现3.1 数据准备3.2 创建逻辑回归模型3.3 模型训练3.4 模型预测3.5 模型评估 4. 可视化决策边界4.1 绘制散点图4.2 绘制决策…

《零基础入门学习Python》第056讲:论一只爬虫的自我修养4:网络爬图

今天我们结合前面学习的知识&#xff0c;进行一个实例&#xff0c;从网络上下载图片&#xff0c;话说我们平时闲来无事会上煎蛋网看看新鲜事&#xff0c;那么&#xff0c;熟悉煎蛋网的朋友一定知道&#xff0c;这里有一个 随手拍 的栏目&#xff0c;我们今天就来写一个爬虫&…

实验三 贪心算法

实验三 贪心算法 迪杰斯特拉的贪心算法实现 优先队列等 1.实验目的 1、掌握贪心算法的基本要素 &#xff1a;最优子结构性质和贪心选择性质 2、应用优先队列求单源顶点的最短路径Dijkstra算法&#xff0c;掌握贪心算法。 2.实验环境 Java 3.问题描述 给定带权有向图G (V…

前端对后端路径的下载//流文件下载

1.前端对后端路径的下载 2.流文件下载

25 MFC 数据库

文章目录 导入ADO库 导入ADO库 #import "C:\Program Files\Common Files\System\ado\msado15.dll" no_namespace rename("EOF","rsEOF")void CADODlg::OnBnClickedBtnQuery() {//导入ADO库::CoInitialize(NULL);//初始化COM库_ConnectionPtr pCo…

OpenCv之人脸操作

目录 一、马赛克实现 二、人脸马赛克 三、人脸检测 四、多张人脸检测 一、马赛克实现 案例代码如下: import cv2 import numpy as npimg cv2.imread(8.jpg) # 马赛克方式一:缩小图片 # img2 cv2.resize(img,(600,400)) # # 马赛克方式二: # img2 cv2.resize(img,(600,4…

《中国算力服务研究报告(2023年)》发布在即

2023年&#xff0c;中共中央 国务院印发《数字中国建设整体布局规划》&#xff0c;强调“系统优化算力基础设施布局&#xff0c;促进东西部算力高效互补和协同联动。”算力服务涵盖支撑东西部算力高效联通的关键技术&#xff0c;在国家政策引导下&#xff0c;我国算力服务产业发…

SpringCloudAlibaba微服务实战系列(一)Nacos服务注册发现

SpringCloudAlibaba微服务实战系列&#xff08;一&#xff09;Nacos服务注册发现 实战前先做一个背景了解。 单体架构、SOA和微服务 单体架构&#xff1a;近几年技术的飞速发展&#xff0c;各种各样的服务已经进入到网络化。单体架构发布时只需要打成一个war或jar包发布即可&a…

动态内存管理基础详解

目录 1、为什么存在动态内存分配 2、动态内存函数的介绍 2.1 malloc和free 功能&#xff1a; 参数和返回值&#xff1a; 注意事项&#xff1a; tip: 2.2 calloc 2.3 realloc函数 功能&#xff1a; 参数和返回值&#xff1a; realloc开辟空间的两种情况 realloc会顺…

SAR合成孔径雷达中几何术语定义

《合成孔径雷达成像算法与实现》 术语定义 目标 波束覆盖区 星下点 雷达轨迹 速度 方位向 零多普勒面 最短距离 最近位置 零多普勒时刻 波束宽度 目标轨迹 信号空间和图像空间 距离 斜距平面 地距 斜视角 距离横向 目标 是被SAR照射的地球表面上的一…