(unordered)map和set封装(底层红黑树)

news2024/11/21 0:32:12

map和set封装

文章目录

  • map和set封装
    • 设计问题(知其所以然)
      • 为什么要对iterator进行封装?
      • 为什么要引入Self Ref Ptr这些模板参数?
      • 为什么是试图从non_const转变为const,而不是const转为non_const
        • 如何解决
      • 为什么说能加const把const加上
      • 增加构造函数解决set调用红黑树实现插入时iterator的转换
        • 解决方法
  • unordered_map/set封装
      • 为什么迭代器中指向哈希表的指针要加const?为什么可以加const?
        • 为什么指针要加const
        • 为什么可以加const,不会影响对哈希表的增加删除吗?
      • 老生常谈的问题
      • 两个由const引发的bug

设计问题(知其所以然)

为什么要对iterator进行封装?

是否需要分装和指向的类型有关:

  • 如果迭代器指向的是内置类型,不需要分装
  • 如果是自定义类型,则需要重载一系列操作符,因此需要分装

在STL的实现中,string的实现就没有用到封装,是因为string的迭代器为char* ,进行*(解引用),++等操作时不需要重载

为什么要引入Self Ref Ptr这些模板参数?

这里和list的底层实现道理一致

因为迭代器有const 和non_const两种,而不同的迭代器种类要有不同的返回值类型,也就是说如果我们不传入模板参数,很多函数要写两次(返回值类型不同),造成代码的冗余

template<class T>
	class list
	{
		.......

	public:
		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;
    }
		

下面的注释可以帮助理解如何解决冗余:

	template<class T,class Ref,class Ptr>
	class list_iterator
	{
		typedef listNode<T> Node;
	public:
		Node* _pnode;
        
		list_iterator(Node* pnode)
			:_pnode(pnode)
		{}

        //const T& 或 T& 
		Ref operator*()
		{
			return _pnode->_val;//->结构体指针访问结构体成员变量的方式
		}
         //const T* 或 T*
		Ptr operator->()
		{
			return &(_pnode->_val);
		}
		
		typedef list_iterator<T, Ref, Ptr> Self;
        //iterator 或 const_iterator
		Self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}
        
        .......
    }

为什么是试图从non_const转变为const,而不是const转为non_const

	class Set
	{
	private:
		struct setKeyofT{...}
		RBTree<K, K, setKeyofT> _t;

	public:
		//底层是对树的封装
		typedef typename RBTree<K, K, setKeyofT>::const_iterator iterator;//两个迭代器都是const迭代器
		typedef typename RBTree<K, K, setKeyofT>::const_iterator const_iterator;
		
		iterator begin()
		{
			return _t.begin();
		}
		const_iterator begin()const
		{
			return _t.begin();
		}

		iterator end()
		{
			return _t.end();//此行报错
		}
		const_iterator end()const
		{
			return _t.end();
		}
	s.Insert(3);
	s.Insert(1);
	s.Insert(6);
	s.Insert(5);
	s.Insert(9);
	s.Insert(4);

	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

报错原因,无法转变树中的迭代器,需要自己实现转变:

在这里插入图片描述

为什么是试图从non_const转变为const:

  • 此时调用的是第一个begin在这里插入图片描述

  • 返回的是树的iterator,但是set的iterator相当于树的const_iterator,类型不匹配而且不能自动发生转换,因此报错

如何解决

set在STL中源码是这样解决的:

在这里插入图片描述

这样写非常的巧妙,大佬不愧是大佬:

这样传进来的_t就有const修饰,调用的是RBTree中const_iterator begin(),const可以满足和set中(const)iterator的配对

解决了上述转化的问题

此时只需要写一个就可以满足要求,多了是重复的,会报错

		typedef typename RBTree<K, K, setKeyofT>::const_iterator iterator;
		typedef typename RBTree<K, K, setKeyofT>::const_iterator const_iterator;
		
		iterator begin()const
		{
			return _t.begin();
		}

		/*const_iterator begin()const
		{
			return _t.begin();
		}*/

在这里插入图片描述

为什么说能加const把const加上

因为权限可以缩小,普通对象是可以调用的,但如果不加const,const对象就无法调用

增加构造函数解决set调用红黑树实现插入时iterator的转换

template<class K>
class Set
{
	
	RBTree<K, K, setKeyofT> _t;
public:
	typedef typename RBTree<K, K, setKeyofT>::const_iterator iterator;
	typedef typename RBTree<K, K, setKeyofT>::const_iterator const_iterator;
		
	pair<iterator, bool> Insert(const K& k)
	{
		return _t.Insert(k);
	}
};

在这里插入图片描述

map可以正常调用而set不行,因为_t调用的红黑树的insert返回的是普通itereator,但是set的iterator实际上是const_iterator,所以报错。要想办法转换

解决方法

构造一个转换的函数拿iterator构造成const_iterator

大佬还是大佬,再来膜拜一下大佬的思路

struct _TreeIterator
{
	.....
        
	typedef _TreeIterator<T,Ref,Ptr> Self;//迭代器本身,受Ref和Ptr影响
    
	typedef _TreeIterator<T, T&, T*> iterator;//始终是普通迭代器,在 _TreeIterator中封装了一个普通迭代器(的类型)
    
	_TreeIterator(const iterator& it)//支持传入普通迭代器调用构造函数
		:_node(it._node)
	{}
    
}
  • 当这个迭代器被实例化为const迭代器,这是一个构造函数
  • 当实例化为普通迭代器,这是一个拷贝构造
    在这里插入图片描述

unordered_map/set封装

为什么迭代器中指向哈希表的指针要加const?为什么可以加const?

为什么指针要加const

HashTable中,const对象调用迭代器,传入的指针是const类型,会造成权限的放大

const_iterator end()const
{
	return const_iterator(nullptr, this);//因为是const,所以this是const
}

在这里插入图片描述

为什么可以加const,不会影响对哈希表的增加删除吗?

迭代器里不用修改哈希表,哈希表的修改是基于Node改变,Node不为const

老生常谈的问题

为什么要在迭代器中增加不受传值影响的iterator迭代器类型(不是对象)

因为set的两个迭代器都是const迭代器,但返回的时候是哈希表中的普通迭代器,因为是自定义类型,需要添加构造函数实现转换(同map和set)

两个由const引发的bug

在这里插入图片描述
在这里插入图片描述

  • 图一:加上const以后,323行调用的end函数为const对象调用的,返回对象为cosnt_iterator,const_iterator不能转换为iterator
    在这里插入图片描述

  • 图二:多加了一个const,在初始化的时候出现错误(const const T*)
    在这里插入图片描述

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

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

相关文章

vscode 乱码解决

windows 10 系统 vs code 编译运行和调试 C/C_vscode windows编译_雪的期许的博客-CSDN博客 VS Code默认文件编码时UTF-8&#xff0c;这对大多数情况是没有问题的&#xff0c;却偏偏对C/C有问题。如果以UTF-8编码保存C/C代码&#xff0c;那么只能输出英文&#xff0c;另外使用…

【Java-LangChain:使用 ChatGPT API 搭建系统-4】评估输入-分类

第三章&#xff0c;评估输入-分类 如果您正在构建一个允许用户输入信息的系统&#xff0c;首先要确保人们在负责任地使用系统&#xff0c;以及他们没有试图以某种方式滥用系统&#xff0c;这是非常重要的。 在本章中&#xff0c;我们将介绍几种策略来实现这一目标。 我们将学习…

优先级队列--合并多个有序链表

如果本文章有不懂的&#xff0c;请进入预习链接&#xff1a;优先级队列_加瓦不加班的博客-CSDN博客 这道题目之前解答过&#xff0c;现在用刚学的优先级队列来实现一下 题目中要从小到大排列&#xff0c;因此选择用小顶堆来实现&#xff0c;自定义小顶堆如下 //小顶堆的操作与…

图像处理与计算机视觉--第五章-图像分割-Canny算子

文章目录 1.边缘检测算子分类2.Canny算子核心理论2.1.Canny算子简单介绍2.2.Canny算子边缘检测指标2.3.Canny算子基本原理 3.Canny算子处理流程3.1.高斯滤波去噪声化3.2.图像梯度搜寻3.3.非极大值抑制处理3.4.双阈值边界处理3.5.边界滞后技术跟踪3.6.Canny算子边缘检测的特点 4…

Linux使用之xshell、xftp保姆教学(含安装包,详细使用方法,连接失败解决方法)

前言 链接: FTP&#xff0c;SSH服务器介绍 这是我之前写的一篇博客&#xff0c;其中介绍了Ubuntu操作系统的一些常用命令以及服务器介绍&#xff0c;这篇文章就向大家详细介绍如何安装及应用这些服务器&#xff0c;我以xshell、xftp为例。 安装包&#xff0c;使用方法&#xf…

认识柔性数组

在C99中&#xff0c;结构中的最后一个元素允许是未知大小的数组&#xff0c;这就叫做柔性数组成员 限制条件是&#xff1a; 结构体中最后一个成员未知大小的数组 1.柔性数组的形式 那么我们怎样写一个柔性数组呢 typedef struct st_type {int i;int a[0];//柔性数组成员 }ty…

GD32F103x IIC通信

1. IIC通信 1.IIC的介绍 IIC总线有两条串行线&#xff0c;其一是时钟线SCK&#xff08;同步&#xff09;&#xff0c;其二是数据线SDA。只有一条数据线属于半双工。应用中&#xff0c;单片机常常作为主机&#xff0c;外围器件可以挂载多个。&#xff08;当然主机也可以有多个。…

【小程序 - 加强】自定义组件、使用npm包、全局数据共享、分包_05

目录 一、自定义组件 1. 组件的创建与引用 1.1 创建组件 1.2 引用组件 1.2.1 局部引用组件 1.2.2 全局引用组件 1.2.3 全局引用 VS 局部引用 1.2.4 组件和页面的区别 2. 样式 2.1 组件样式隔离 2.2 组件样式隔离的注意点 2.3 修改组件的样式隔离选项 2.4 styleIso…

stable diffusion学习笔记【2023-10-2】

L1&#xff1a;界面 CFG Scale&#xff1a;提示词相关性 denoising&#xff1a;重绘幅度 L2&#xff1a;文生图 女性常用的负面词 nsfw,NSFW,(NSFW:2),legs apart, paintings, sketches, (worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, (…

位置编码器

目录 1、位置编码器的作用 2、代码演示 &#xff08;1&#xff09;、使用unsqueeze扩展维度 &#xff08;2&#xff09;、使用squeeze降维 &#xff08;3&#xff09;、显示张量维度 &#xff08;4&#xff09;、随机失活张量中的数值 3、定义位置编码器类&#xff0c;我…

SpringBoot 可以同时处理多少请求

一、前言 首先&#xff0c;在Spring Boot应用中&#xff0c;我们可以使用 Tomcat、Jetty、Undertow 等嵌入式 Web 服务器作为应用程序的运行容器。这些服务器都支持并发请求处理的能力。另外&#xff0c;Spring Boot 还提供了一些配置参数&#xff0c;可以对 Web 服务器进行调…

传输层协议——TCP、UDP

目录 1、UDP 协议&#xff08;用户数据报协议&#xff09; 协议特点 报文首部格式 2、TCP 协议&#xff08;传输控制协议&#xff09; 协议特点 报文首部格式 TCP连接建立时的三次握手 TCP拆除连接的四次挥手 TCP的流量控制 TCP的拥塞控制 3、传输层端口号 三类端口…

一维数组和二维数组的使用(int类型)

目录 导读1. 一维数组1.1 一维数组的创建1.2 数组的初始化1.3 一维数组的使用1.4 一维数组在内存中的存储 2. 二维数组2.1 二维数组的创建2.2 二维数组的初始化2.3 二维数组的使用2.4 二维数组在内存中的存储 博主有话说 导读 本篇主要讲解一维数组和二维数组的创建和使用&…

【软考】4.2 关系代数

《 关系代数 》 表和表之间的逻辑运算 笛卡尔积&#xff1a;S1 x S2 投影&#xff1a;π&#xff1b;选择某一列&#xff08;属性&#xff09;&#xff1b;一个关系R的投影操作结果也是一个关系&#xff0c;记作Πa&#xff0c;它由从关系R中选出的A列元素构成&#xff1b;选择…

React18入门(第二篇)——React18+Ts项目配置husky、eslint、pretttier、commitLint

前言 我的项目版本如下&#xff1a; React&#xff1a; V18.2.0Node.js: V16.14.0TypeScript&#xff1a;最新版工具&#xff1a; VsCode 本文将采用图文详解的方式&#xff0c;手把手带你快速完成在React项目中配置husky、prettier、commitLint&#xff0c;实现编码规范的统…

OpenNebula的配置与应用(一)

学习了OpenNebula的安装之后&#xff0c;接下来就是配置OpenNebula&#xff0c;内容包括配置Sunstone&#xff0c;VDC和集群&#xff0c;设置影像&#xff0c;模板管理&#xff0c;虚拟机管理等。OpenNebula还有大量的工作要做&#xff0c;这些工作主要来自映像、模板和虚拟机管…

[架构之路-225]:计算机硬件与体系结构 - 分类方法大汇总: RISC, CISC

目录 一、分类方法汇总 二、指令流和数据流的关系分类 三、Flynn 分类&#xff1a;指令并行处理 四、根据指令集架构&#xff08;ISA&#xff09;分类 4.1 分类 4.2 开源的RISC-V与封闭的RISC指令集架构比较 4.3 RISC-V的演进路径 4.4 RISC-V与中国芯片自研 4.4 五阶流…

服务网关Gateway_入门案例

创建cloud-gateway-gateway9527工程 pom文件引入依赖 <dependencies><!-- 引入网关Gateway依赖 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></depe…

基于SpringBoot的社区医院管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

修改npm全局安装的插件(下载目录指向)

我们先打开终端 然后执行 npm config get prefix查看npm 的下载地址 一般都会在C盘 但是 我们都知道 C盘下东西多了是很不好的 所以 我们可以执行 npm config set prefix “E:\npmfile”将 npm 的下载地址 改变成 E盘下的 npmfile目录 这样 以后 默认全局安装的插件就会都到…