c++编程(17)——deque的模拟实现(1)迭代器篇

news2024/10/5 20:27:08

欢迎来到博主的专栏——c++编程
博主ID:代码小豪

博主模拟STL中的容器时,参考的是SGI版本的STL,如果你对STL的源码感兴趣,请私聊博主。

文章目录

    • deque的底层原理
    • deque的迭代器
      • deque迭代器的操作
      • 迭代器的随机访问操作

deque的底层原理

deque是线性表的一种,可以像vector一样,实现随机访问,也可以向链表一样,进行头部的插入和删除(并不是vector不能进行头部的数据插入与删除,而是vector进行头部和删除的效率太低了,时间复杂度是O(N),而deque是O(1)),这是deque特殊的数据结构导致的。

在这里插入图片描述

deque有着像list那样高效率的头尾插入,也能像vector那样随机访问,这是由于deque的空间并不像list那样无序,又不像vector那样的顺序空间,而是一块块零散的顺序空间来存储数据。由于deque并非完全连续,而是以分段的连续空间组成,这就导致deque可以不像vector那样繁杂的复杂的重新配置空间的操作。

deque是由分段的连续空间组成的,其结构如下:
在这里插入图片描述

由于不像vector那样是完全连续的数据结构,因此deque虽然能随机访问,但是效率要比vector低很多(因为会出现跨区访问的操作,这需要一定的时间开销)如果deque的前端空间或者后端空间满了,就会在deque的前端或后端增加新空间,以保持deque顺序存储的假象。

在这里插入图片描述

如果你对数据结构具有一定理解,那么你一定会对这个数据结构进行质疑:仅凭一个begin和end指针,是怎么做到管理分段的连续数组呢?当然,如果只是两个指针当然做不到。实际上deque的数据结构是这样子的。

deque中存在一个map数组,其元素是指向各个分段空间的指针,这么一来就好理解了。如果map数组拥有所有分段空间的指针,那么管理这些分段数组确实可行的,由于这个map数组有点像中央控制器,因此我们就将map称为中控数组吧。

在这里插入图片描述
中控数组map指向空间称为缓冲区(buffer),每个缓冲区可容纳的元素个数都是一致的,如果我们在deque的模板上定义一个非类型模板参数,那么这一点还是很好做到的。

template<class T,size_t bufsize=25>//bufsize是deque缓冲区的元素个数
class deque	{

private:
};

但是单靠两个指针还是不能做到遍历整个deque容器啊,你想想,如果我们让begin指针++,那么begin就会越界访问,除非我们能让begin进行空间上的跳跃。
在这里插入图片描述
但是指针++只会让指针访问下一个元素,它是绝对不会进行跳跃的。那么我们就要设计deque的专属迭代器了。我们先不管deque的迭代器是什么样的,我们假设它的名字叫做deque_iterator。那么deque的定义如下:

	template<class T,class ptr,class ref,size_t bufsize>
	struct deque_iterator {//deque的迭代器

	};

	template<class T,size_t bufsize=25>//bufsize是deque缓冲区的元素个数
	class deque
	{
		typedef T* pointer;
		typedef pointer* map_poniter;//中控数组的类型
	private:
		deque_iterator first;//指向数组第一个元素的迭代器
		deque_iterator last;//指向数组最后一个元素后一位的迭代器
		map_poniter map;//中控数组
		size_t mapsize;//数组的个数
	};

deque的迭代器

deque实现随机访问的操作,其代价就是迭代器设计被设计的非常复杂。我们先来看看deque的迭代器需要完成哪些操作。
在这里插入图片描述
假设现在迭代器first指向第一个元素,如过访问first的下一个元素,first就会指向当前顺序空间的下一个元素,而如果要访问first的下两个元素,即first+2,那么就要让first实现空间上的跳跃。因此deque的迭代器应该具有以下的结构:

  1. 首先迭代器需要知道当前所在的缓冲区在哪里
  2. 迭代器需要知道当前是否处于当前缓冲区的边缘,这样才能判断访问的元素是否超过了当前的缓冲区
  3. 如果超过了当前的缓冲区,就要跳跃到下一个缓冲区,因此迭代器需要访问到中控中心map来获得下一个缓冲区的位置

而SGI版本的STL是这么设计deque的迭代器的
deque的迭代器拥有四个成员,分别是first,last,node,以及cur,其各个成员的作用如下:

first指向当前的缓冲区的起始地址
last指向当前缓冲区的末尾地址
node指向map中的当前缓冲区,便于跳跃到下一个缓冲区。
cur指向缓冲区的当前元素(即访问的当前元素)

在这里插入图片描述

template<class T,class ptr,class ref,size_t bufsize>
struct deque_iterator {//deque的迭代器
typedef T* pointer;
typedef pointer* map_pointer;
typedef deque_iterator self;//这个self是迭代器的别名

//data member
T* first;
T* last;
T* cur;
map_pointer node;
};

根据c++规定,每个容器的迭代器都要放在类中,并且起一个同一的别名,因此我们还要在deque当中typedef这个迭代器

template<class T,size_t bufsize=25>//bufsize是deque缓冲区的元素个数
class deque
{
//省略
public:
	typedef deque_iterator<T,T*,T&,bufsize> iterator;//为迭代器起一个别名
	typedef deque_iterator<T,const T*,const T&,bufsize> const_iterator;//为迭代器起一个别名
//省略
};

deque迭代器的操作

deque的迭代器有一个很重要的操作就是让迭代器获得跳跃缓冲区的能力,实际上也就是从map数组中的一个位置来到另一个位置,比如
在这里插入图片描述
如果我们要访问下一个缓冲区,就让node指向map数组中的下一个元素即可
在这里插入图片描述
当然了,迭代器的first和last必须保持指向node缓冲区的起始地址和结束地址。因此完成这个操作的函数我们用setnode()表示,该函数的定义如下:

void setnode(map_poniter newnode)
{
	node = newnode;
	first = *node;
	last = first + bufsize;
}

在这里插入图片描述
deque的迭代器支持两个迭代器进行相减得到之间的元素个数,这也是deque可以实现随机访问的重要原因(list就不支持)。

int operator-(const self& x)
{
	return int(node - x.node - 1) 
		+ cur - first 
		+ x.last - x.cur;
}

在这里插入图片描述
迭代器2减去迭代器1,其蓝色部分就是两个迭代器相减的元素个数。两个迭代器之间的元素个数等于,两个迭代器之间的缓冲区(不包括本身的缓冲区)的所有元素,加上迭代器2到起始为止的元素,再加上迭代器1到末尾的元素个数。

self& operator++(){
	++cur;//访问下一个元素
	if(cur==last){//如果下一个元素来到了缓冲区的结尾
		setnode(node + 1);//就来到下一个缓冲区
		cur = first;//cur指向新缓冲区的第一个元素
	}
	return *this;
}

self operator++(int) {//后置++
	self tmp = *this;
	(*this)++;
	return tmp;
}

当迭代器访问下一个元素时,可能会出现以下两种情况

  1. 如果访问的下一个元素还在缓冲区内(cur!=last-1),就让cur访问顺序结构的下一个元素
  2. 如果访问的下一个元素不在缓冲区内,就让迭代器跳到下一个缓冲区,且cur指向缓冲区的起始地址

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

self& operator--(){
	if (cur == first){//判断cur是否来到了当前缓冲区的起始位置
		setnode(node - 1);//让node跳跃到上一个缓冲区
		cur = last - 1;//让cur指向末尾位置
	}
	else {
		cur--;
	}
	return *this;
}

self operator--(int){//后置--
	self tmp = *this;
	(*this)--;
	return tmp;
}

让迭代器访问上一个元素的原理和operator++类似

  1. 如果迭代器指向的元素所处的缓冲区前面还有元素,就让迭代器访问顺序结构的上一个元素
  2. 如果迭代器指向的元素来到了缓冲区的起始地址,就要跳往上一个缓冲区,并且让迭代器的cur指向缓冲区的末尾元素(cur=last-1)
    在这里插入图片描述
    在这里插入图片描述

迭代器的其他操作

bool operator==(const self& x)const
{
	cur == x.cur;
}
		
bool operator!=(const self& x)const
{
	cur != x.cur;
}

bool operator<(const self& x)const
{
	return node == x.node ? cur < x.cur : node < x.node;
}

迭代器的随机访问操作

self& operator+=(int n)
{
	int offset = n + (cur - first);
	if (offset >= 0 && offset < bufsize)//判断是否在缓冲区内
	{
		cur += n;
	}
	else//目标不在缓冲区内
	{
		int offset_node =
			offset > 0 ? offset / int(bufsize) : ((offset + 1) /(int) bufsize) - 1;
		setnode(node + offset_node);//切换到正确的缓冲区
		cur = first + offset - offset_node * bufsize;//切换到正确的位置
	}
}

这段代码中最难以理解的就是变量offset了,这个offset简单理解为"偏移量",指的是随机访问的元素与first之间数据。比如,我们假设cur指向第一个元素,让cur+=8;
在这里插入图片描述
首先我们先获得偏移量,offset=4+8=12;由于偏移量大于bufsize(6),因此我们确定cur+=8的结果不在本缓冲区内。然后让offset/bufsize=12/6=2,于是确定cur+=的结果在后两个缓冲区当中,于是setnode让迭代器来到后两个迭代器。最后计算offset-offset_node*bufsize=0。说明要查找的元素在当前缓冲区的后两个缓冲区中相对于first的第0个元素。因此cur+=8的结果为。

![![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2e068d1bc9314517a7a6a1c1a522dde4.png)

为什么相对于first的第0个元素在这里呢?这是因为C的数组下标总是从0开始计算的。

其他的操作只需要复用operator+=即可。因为我们已经允许+=对负数n进行处理。


self operator+(int n)
{
	self tmp = *this;
	return tmp += n;
}

self& operator-=(int n)
{
	return *this += (-n);
}

self operator-(int n)
{
	self tmp = *this;
	return tmp -= (-n);
}
		
ref operator[](int n)
{
	return *(*this + n);
}

到此为止,我们的迭代器已经设计好了,由于篇幅原因,博主将deque容器的实现放在下一篇博客当中叙述(因为两篇加起来大约2w字,实在是有点难啃)。

链接如下:
deque_iterator的实现代码

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

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

相关文章

类别不平衡

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、介绍1、过采样2、欠采样 二、过采样1、SMOTE&#xff08;常用&#xff09;1、算法流程2、算法实现3、参数介绍 2、ADASYN&#xff08;不常用&#xff09;1、算法流程…

车票信息的请求与显示

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 1 发送与分析车票信息的查询请求 得到了获取车票信息的网络请求地址&#xff0c;然后又分析出请求地址的必要参数以及车站名称转换的文件&#xff…

程序猿大战Python——函数——嵌套调用与变量作用域

嵌套调用及执行流程 目标&#xff1a;了解函数的嵌套调用。 函数的嵌套调用指的是&#xff1a;在一个函数中&#xff0c;调用了另一个函数。 嵌套调用语法&#xff1a; def 函数1():代码... ​ def 函数2():代码# 调用函数1函数1()... 说明&#xff1a; 在函数2中&#xff0c…

8个宝藏APP,个个都牛逼哈拉!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 目前win7已经逐渐淡出人们的视野&#xff0c;大部分人都开始使用win10&#xff0c;在日常工作和使用中&#xff0c;创客们下载神奇的软件能大幅提…

怎样快速清理电脑里的所有软件 怎么删除干净电脑软件

苹果电脑内的软件来源主要有两个&#xff0c;一是系统预装&#xff0c;二是用户自行下载。但并不是所有应用程序都是高频使用状态&#xff0c;甚至好多是从未打开过的“屏幕装饰”。小编今日独家攻略&#xff0c;内存告急如何快速清理电脑里的所有软件&#xff0c;怎么删除干净…

线程池ThreadPoolExecutor使用指南

线程池ThreadPoolExecutor使用指南 &#x1f9d0;使用线程池的好处是什么&#xff1f; 统一管理&#xff0c;减少资源获取创建的开销&#xff0c;提高利用率。 &#x1f527;线程池的参数 ​ThreadPoolExecutor​ 3 个最重要的参数&#xff1a; ​corePoolSize​ : 任务队列…

问:IP写作如何商业化?

这个问题也是很多朋友&#xff0c;或者新手小白问的最多的问题&#xff0c; 毕竟我们做副业都是为了挣钱嘛&#xff0c; 那么&#xff0c;回到问题&#xff0c;IP写作如何商业化&#xff1f; 这个问题其实对于我现在要日更的目标来说为时尚早&#xff0c;不过也可以先了解一下。…

Javaweb登录校验

登录校验 JWT令牌的相关操作需要添加相关依赖 <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version> </dependency>一、摘要 场景&#xff1a;当我们想要访问一个网站时&am…

如何用AI提高产品经理的工作效率

最近我跟几个产品经理聊天&#xff0c;发现有些人居然还没有使用过ChatGPT、MidJourney、NotionAI 等AI工具。 产品经理有个重要的素质是好奇心&#xff0c;好奇心能够帮助产品经理发现新机会、了解用户需求、学习新知识和探索竞争对手&#xff0c;从而更好地完成产品开发和管…

mediamtx流媒体服务器测试

MediaMTX简介 在web页面中直接播放rtsp视频流&#xff0c;重点推荐&#xff1a;mediamtx&#xff0c;不仅仅是rtsp-CSDN博客 mediamtx github MediaMTX(以前的rtsp-simple-server)是一个现成的和零依赖的实时媒体服务器和媒体代理&#xff0c;允许发布&#xff0c;读取&…

android13 应用冷启动

1 概述 launcher 通过binder到systemserver中atms中发送startActivity请求 startProcess向zygote发送启动新进程请求 zygote收到请求&#xff0c;fork新进程并调用ActivityThread的main初始化 新进程启动&#xff0c;发送attachApplication给ams&#xff0c;告诉他新进程启动…

C++ 44 之 指针运算符的重载

#include <iostream> #include <string> using namespace std;class Students04{ public:int m_age;Students04(int age){this->m_age age;}void showAge(){cout << "年龄是&#xff1a; " << this->m_age << endl;}~Students0…

155. 最小栈 力扣 python 空间换时间 o(1) 腾讯面试题

设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int get…

RestTemplate远程请求的艺术

1 简说 编程是一门艺术,追求优雅的代码就像追求优美的音乐。 很多有多年工作经验的开发者,在使用RestTemplate之前常常使用HttpClient,然而接触了RestTemplate之后,却愿意放弃多年相处的“老朋友”,转向RestTemplate。那么一定是RestTemplate有它的魅力,有它的艺术风范。…

CCAA质量管理【学习笔记】​​ 备考知识点笔记(三)质量管理方法与常见工具

第二部分 质量管理领域专业知识 《质量管理体系基础考试大纲》中规定的考试内容&#xff1a; 3.2 质量管理领域专业知识 a) 了解质量管理方法与工具相关知识&#xff0c;包括&#xff1a; 质量管理方法与工具的内涵与作用、发展历程与应用现状、分类与选择常用的应用软件…

使用 Vue CLI 脚手架生成 Vue 项目

最近我参与了一个前端Vue2的项目。尽管之前也有过参与Vue2项目的经验&#xff0c;但对一些前端Web技术并不十分熟悉。这次在项目中遇到了很多问题&#xff0c;所以我决定借此机会深入学习Vue相关的技术栈。然而&#xff0c;直接开始深入钻研这些技术可能会显得枯燥&#xff0c;…

随笔-来了,安了

依照领导定的规矩&#xff0c;周五又去了分公司&#xff0c;赋能一线去了。到了地方就是开会->现场解决问题->干饭->开会过需求、提供解决方案&#xff0c;充实得厉害。强度也不小&#xff0c;中午干的一大碗饭&#xff0c;到五点就饿了。 六点带着分公司催着上线的需…

jupyter notebook中使用不同的anaconda环境及常用conda命令

conda命令 在jupyter notebook中使用不同的anaconda环境其他常用conda命令 在jupyter notebook中使用不同的anaconda环境 创建环境 myenvname 需替换为自己的环境名称 conda create --name myenvname python3.7激活环境 conda activate myenvname 在该环境中安装Jupyter N…

MongoDB~事务了解;可调一致性模型功能与因果一致性模型功能分析

背景 MongoDB 从 3.0版本引入 WiredTiger 存储引擎之后开始支持事务&#xff0c;MongoDB 3.6之前的版本只能支持单文档的事务&#xff0c;从 MongoDB 4.0版本开始支持复制集部署模式下的事务&#xff0c;从 MongoDB 4.2版本开始支持分片集群中的事务。 根据官方文档介绍&…

C++11 move左值转化为右值

单纯的左值只能用左值引用和右值只能用右值引用有些局限&#xff0c;在一些情况下&#xff0c;我们也需要对左值去调用右值引用&#xff0c;从而实现将左值里的内容转移到右值中 标准定义&#xff1a; 功能就是将一个左值强制转化为右值&#xff0c;然后实现移动语义 注意&…