list模拟
之前模拟了string,vector,再到现在的list,list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表,该结构虽然看起来比较复杂,但是却非常有利于我们做删除节点的操作,结构图如下。
由于其节点结构特点,使得头插头删,尾插尾删的时间复杂度为O(1),因为我们只需要改变两边节点的指针链接即可。
节点类
先来个开胃小菜,了解了解节点类,该类比较简单。由于每个节点既要存储数据,又要存储前后节点指针,所以我们封装一个结构体,用的struct而不是class, 因为我们在后面的函数需要访问节点存储的节点指针成员来达到遍历的效果,所以用的是struct。至于封装性,别人也不敢直接访问我节点存的指针变量,举个例子,如果他拿到了我的节点指针ptr,访问成员用ptr->+(成员名字),问题就在成员名字,每个人实现的类名函数名那都是五花八门,这就导致这段访问成员的代码就不具有平台移值性。
template <class T> struct list_node{typedef list_node<T> node; 这里记忆一下node是节点类型名的重命名,后面还有很多重命名,免得搞混了。list_node(const T& val=T())T()是一个匿名对象,当我们未传对象初始化时,可用一个匿名对象来初始化value:_next(nullptr), _prev(nullptr) 用初始化列表来初始化,因为_val存的若是自定义类型, _val(val) 想要调用非默认构造函数得用初始化列表。 {;}node* _next;node* _prev;T _val;};
链表正向迭代器类:
不知道还记不记得string和vector的迭代器,string的iterator就是char*的重命名,而vector则是T*的重命名,那为什么list的迭代器不能把node*直接typedef呢,然后begin()函数返回哨兵位的下一个节点地址,end()函数返回哨兵位节点地址。假设我们就这样实现list的迭代器,然后结合下面的使用场景。
my_list::list<int>::iterator it = l1.begin();
while (it != l1.end())
{it++;
}
大家有没有想过it++到哪去了呢?是下一个节点吗?当然不是啦,链表的每个节点都是孤立的,你++怎么能到下一个节点呢?当时我立马就想到运算符重载了,在重载函数内部++指针是往后走,还是往前走,又或者是++到指针变成下一个节点地址那不都是我们实现者说了算。冷静,冷静,运算符重载首要原则就是操作数应该是自定义类型,所有类型的指针都是内置类型,所以节点指针它就没办法重载运算符,所以我们要把节点指针封装成类,这样我们才可以对这个类进行运算符重载,使得it++调用类内重载函数,到下一个节点。
template<class T,class Ref,class Ptr> Ref和Ptr这两个模板参数是为了实现const迭代器struct _list_iterator 使其成员函数可在类外访问{typedef list_node<T> node;typedef _list_iterator<T,Ref,Ptr> Self;_list_iterator(node* pnode=nullptr)//用一个node指针初始化其成员:_node(pnode){;}//无析构函数,迭代器不能释放节点_list_iterator(const Self& l)//临时对象的引用,例如接受begin():_node(l._node){;}Self& operator++()//this指针是个迭代器类型的指针{_node = _node->_next;return (*this);}Self operator++(int)首先这两个++运算符重载还构成函数重载编译默认给前置++多传一个参数,使其调用这个函数,如果你把这两个函数参数换一下,前置++去掉int,后置++加上int你就会发现前置++会去调用后置++的函数,因为编译器还是用参数匹配或者函数名修饰规则来找函数。{Self tmp = (*this);_node = _node->_next;return (tmp);}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self tmp = (*this);_node = _node->_prev;return tmp;}
bool operator!=(const Self& l)//要加const,因为外部可能是与end()比较{return (_node != l._node);}bool operator==(const Self& l){return (_node == l._node);//判断迭代器内部的node指针是否相等}因为普通迭代器就一两个成员函数的返回值不同,比较简单的方法是用模板参数Ref控制返回const T&还是返回T&,Ref operator*(){return (_node->_val);//返回节点数据}//用类封装节点指针,使其可以重载++,*等运算符node* _node;};
我们实现的const迭代器并不是直接对迭代器类加个const,而是对特定的函数返回值做const修饰,所以迭代器成员函数无需加const.
->重载针对的是节点数据为自定义类型A,A类型存着一个变量_val,的时候,比如有一个类实例化对象为it, 我们希望重载->,使得下面的能调用到自定义类型内的变量,it->_val,但是it->返回的只是节点存的整个结构体A的地址,我们应该用it->->_val才访问到_val,而且这个重载也只能返回
地址,如果返回的是一个结构体对象,我们就要it->.(这有个点操作符)_val才能访问,更加奇怪,
但是当我们去测试的时候发现我们仅仅用it->_val即可访问,实际上是编译器会帮我们多加一个->,为了保证重载运算符的可读性,只能这样改了。还有如果节点存的数据成员是内置类型,我想应该是不可以用->重载的,你想返回的是int*, 但->必须是指向类的。
Ptr operator->()const第三模板参数原因,控制返回const T*或T*,数据不可修改{return &operator*(); }
反向迭代器类
针对list链表,我们可以直接定义一个反向迭代器类叫reverse_iterator,反向迭代器中的成员函数只有++,--和正向迭代器不一样,所以看完正向迭代器的成员函数也就能理解反向迭代器的成员 *解引用重载都是返回节点数据,->重载都是调用operator*()重载再取地址。
namespace my_list
{template<class iterator, class Ref, class Ptr>class reverse_iterator{public:typedef reverse_iterator<iterator, Ref, Ptr> Self;reverse_iterator(iterator it):_it(it)显示调用正向迭代器的拷贝构造{;}reverse_iterator(const Self& l):_it(l._it){;}Self& operator++() 复用正向迭代器_it的--函数{_it--;return (*this);}Self operator++(int){Self tmp = (*this);_it--;return (tmp);}Self& operator--() 复用正向迭代器_it的++函数{_it++;return *this;}Self operator--(int){Self tmp = (*this);_it++;return tmp;}bool operator!=(const Self& l){return (_it != l._it); 此处调用的是正向迭代器内部的比较}bool operator==(const Self& l){return (_it == l._it);}Ref operator*(){iterator tmp = _it;return *(--tmp);}Ptr operator->(){return &operator*();}private:iterator _it;};
}
链表类
我们知道目前设计的链表节点会保存前后位置的指针,这意味着链表类只需要管理头节点指针即可管理所有链表节点,有哨兵位则保存哨兵位地址,没有则保存第一个节点的地址。
template<class T>class list{typedef list_node<T> node;//设为私有,不让外部通过该处访问public:typedef _list_iterator<T, T&, T*> iterator;//设为公有typedef _list_iterator<T, const T&, const T*> const_iterator;调用const迭代器就传const T&和const T*控制函数返回值不被修改就达到const迭代器作用了下面这两个迭代器的typedef顺序不能改,
不然可能会将reverse_iterator<const_iterator, const T&, const T*>中的reverse_iterator识别为已经typedef的reverse_iterator<iterator, T&, T*>typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;typedef reverse_iterator<iterator, T&, T*> reverse_iterator;reverse_iterator rbegin(){return ((reverse_iterator)end());}const_reverse_iterator rbegin() const{return (const_reverse_iterator)end();}reverse_iterator rend(){return (reverse_iterator)begin();}const_reverse_iterator rend() const{return (const_reverse_iterator)begin();}iterator begin(){return _phead->_next;//返回匿名对象}iterator end(){return _phead;}const_iterator begin()const{return _phead->_next;//返回匿名对象}const_iterator end()const 要加const,要让const list<T>调用该函数,返回const迭代器{return _phead;}void Createhead(){_phead = new node;_phead->_next = _phead;_phead->_prev = _phead;}list(){Createhead();}list(const list<T>& l)//list的拷贝构造{Createhead();for (auto& c : l){push_back(c);}}list(size_t n, const T& value = T()){Createhead();while (n--){push_back(value);}}list(int n, const T& value = T()){Createhead();while (n--){push_back(value);}}template<class inputiterator>list(inputiterator first, inputiterator last) 用一段迭代器区间初始化{Createhead();while (first != last){push_back(*first);++first;}}void swap(list<int>& lt){std::swap(_phead, lt._phead);}list<T>& operator=(list<int> lt)//list<int>可用list代替{swap(lt);return *this;}~list(){clear();delete _phead;_phead = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}void push_front(const T& value = T()){insert(begin(), value);}void push_back(const T& value = T()){//node* newnode = new node(value);//node* tail = _phead->_prev;链接新节点,注意链接关系//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _phead;//_phead->_prev = newnode;insert(end(), value);}iterator insert(iterator pos, const T& value = T()){node* newnode = new node(value);node* pose = pos._node;node* cur = pose->_prev;cur->_next = newnode;newnode->_prev = cur;newnode->_next = pose;pose->_prev = newnode;return newnode;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}size_t size()const{int n = 0;iterator it = begin();while (it != end()){it++;n++;}return n;}bool empty()const{return size() == 0;}iterator erase(iterator pos){node* pose = pos._node;//迭代器it是一个类,访问成员变量用.操作符pos._node = pos._node->_next;node* cur = pose->_prev;cur->_next = pose->_next;pose->_next->_prev = cur;delete pose;return pos;}private:node* _phead;};
}
上述我们封装了四个类,虽然一下子不知道如何说清楚为什么要分开描述,如果都挤在一起,那肯定是有点冗余,类本质是用来描述的,个人认为一种类应该是只描述一种对象的,如果一个类既描述链表节点,又描述链表,我感觉对于我后续的实现会很麻烦。
相关文章:

list模拟
之前模拟了string,vector,再到现在的list,list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表,该结构虽然看起来比较复杂,但是却非常有利于我们做删除节点的操作,结构图如下。 由于其节点结构特…...

python字典:怎么取出key对应的值
目录 python中的字典是什么 怎么判断key是否在字典中 怎么取出key对应的值 总结 python中的字典是什么 在Python中,字典(Dictionary)是一种无序且可变的数据类型,用于存储键-值(Key-Value)对。字典通过…...

okvis
论文 Keyframe-Based Visual-Inertial SLAM Using Nonlinear Optimization 摘要 由于两种感知模式的互补性,视觉和惯性线索的融合在机器人中变得很流行。虽然迄今为止大多数融合策略都依赖于过滤方案,但视觉机器人界最近转向了非线性优化方法&#x…...
fabric js双击弹出菜单, 双击弹出输入框 修改文字 群组对象
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>测试1</title><style type"text/css">body {background-color: #ccc;float: left;}#main {background-color: #fff;border: 1px…...

路由器工作原理
路由器原理 路由概述 路由:跨越从源主机到目标主机的一个互联网络来转发数据包的过程。(为数据包选择路径的过程) 作用:路由器是连接不同网段的。 转发依据: 路由表:路径选择全看路由表,根…...

在centos 7系统docker上构建mysql 5.7
一、VM上已经安装centos 7.9,且已完成docker的构建 二、安装mysql5.7 安装镜像:[rootlocalhost lll]# docker pull mysql:5.7 查看镜像[rootlocalhost lll]# docker images 根据镜像id构建mysql容器,且分配端口号[rootlocalhost lll]# dock…...

数据库的介绍和分类
目录 一、数据库的介绍和分类 二、命令行客户端 三、数据操作 四、查询的基本操作 五、条件查询 六、分组和聚合 资料获取方法 一、数据库的介绍和分类 数据库:长期存储在计算机内、有组织的数据集合 数据库的分类: 关系型数据库 以表格的形式…...
职责链模式——请求的链式处理
1、简介 1.1、概述 很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如SCM系统中的采购单审批,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构。采购单沿着这条链进行传递ÿ…...

docker中涉及的挂载点总结
文章目录 1.场景描述2. 容器信息在主机上位置3. 通过docker run 命令4、通过Dockerfile创建挂载点5、容器共享卷(挂载点)6、最佳实践:数据容器 1.场景描述 在介绍VOLUME指令之前,我们来看下如下场景需求: 1ÿ…...

elasticsearch 官方优化建议
.一般建议 a.不要返回过大的结果集。这个建议对一般数据库都是适用的,如果要获取大量结果,可以使用search_after api,或者scroll (新版本中已经不推荐)。 b.避免大的文档。 2. 如何提高索引速度 a.使用批量请求。为了…...
Kubernetes(K8s)从入门到精通系列之五:K8s的基本概念和术语之应用类
Kubernetes K8s从入门到精通系列之五:K8s的基本概念和术语之应用类 一、Service与Pod二、Label与标签选择器三、Pod与Deployment四、Service的ClusterIP地址五、Service的外网访问问题六、有状态的应用集群七、批处理应用八、应用配置问题九、应用的运维一、Service与Pod Ser…...

DevOps(四)
CD(二) 1. CDStep 1 - 上传代码Step 2 - 下载代码Step 3 - 检查代码Step 4 - 编译代码Step 5 - 上传仓库Step 6 - 下载软件Step 7 - 制作镜像Step 8 - 上传镜像Step 9 - 部署服务2. 整体预览2.1 预览1. 修改代码2. 查看sonarqube检查结果3. 查看nexus仓库4. 查看harbor仓库5.…...

Element-plus侧边栏踩坑
问题描述 el-menu直接嵌套el-menu-item菜单,折叠时不会出现文字显示和小箭头无法隐藏的问题,但是实际开发需求中难免需要把el-menu-item封装为组件 解决 vue3项目中嵌套两层template <template><template v-for"item in list" :k…...

支持多种通信方式和协议方便接入第三方服务器或云平台
2路RS485串口是一种常用的通信接口,可以支持Modbus Slave协议,并可接入SCADA、HMI、DSC、PLC等上位机。它还支持Modbus RTU Master协议,可用于扩展多达48个Modbus Slave设备,如Modbus RTU远程数据采集模块、电表、水表、柴油发电机…...

使用 OpenCV 进行图像模糊度检测(拉普拉斯方差方法)
写在前面 工作中遇到,简单整理人脸识别中,对于模糊程度较高的图像数据,识别率低,错误率高。虽然使用 AdaFace 模型,对低质量人脸表现尤为突出。但是还是需要对 模糊程度高的图像进行丢弃处理当前通过阈值分类ÿ…...

神经网络简单介绍
人工神经网络(artififial neural network) 简称神经网络,它是一种模仿生物神经网络结构和功能的非线性数学模型。 神经网络通过输入层接受原始特征信息,再通过隐藏层进行特征信息的加工和提取,最后通过输出层输出结果。 根据需要神经网络可以…...

16位S912ZVML32F3MKH、S912ZVML31F1WKF、S912ZVML31F1MKH混合信号MCU,适用于汽车和工业电机控制应用。
S12 MagniV微控制器是易于使用且高度集成的混合信号MCU,非常适合用于汽车和工业应用。S12 MagniV MCU提供单芯片解决方案,是基于成熟的S12技术的完整系统级封装 (SiP) 解决方案,在整个产品组合内软件和工具都兼容。 S12 MagniV系统级封装 (S…...

力扣 509. 斐波那契数
题目来源:https://leetcode.cn/problems/fibonacci-number/description/ C题解1:根据题意,直接用递归函数。 class Solution { public:int fib(int n) {if(n 0) return 0;else if(n 1) return 1;else return(fib(n-1) fib(n-2));} }; C题…...
使用 DolphinDB TopN 函数探索高效的Alpha因子
DolphinDB 已经有非常多的窗口计算函数,例如 m 系列的滑动窗口计算,cum 系列累计窗口计算,tm 系列的的时间窗口滑动计算。但是所有这类函数都是对窗口内的所有记录进行指标计算,难免包含很多噪音。 DolphinDB 的金融领域用户反馈…...

超聚变和厦门大学助力兴业银行构建智慧金融隐私计算平台,助力信用卡业务精准营销...
兴业银行与超聚变数字技术有限公司、厦门大学携手,发挥产学研用一体化整体优势联合建设,厦门大学提供先进的算法模型及科研能力,超聚变提供产品解决方案及工程能力,兴业银行提供金融实践能力,三方发挥各自领域优势&…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...