List 模拟实现

前言

本文将会向你介绍如何模拟实现list、iterator迭代器

模拟实现

引入

迭代器是一种用于访问容器中元素的对象,它封装了对容器中元素的访问方式。迭代器提供了一组操作接口,可以让我们通过迭代器对象来遍历容器中的元素。(iterator迭代器内部成员变量和成员函数都是与节点有关的)

先举一个例子,以下是迭代器++成员函数的实现
Self看不懂没关系,我们现在只要知道它是迭代器类名typedef过来的就好了,_pNode是一个指向ListNode结构体的指针。在ListIterator类中,_pNode用于指向链表中的某个节点,通过该指针可以访问节点的成员变量和成员函数。本质上还是对节点做操作,那我们为什么要大费周章还要套一个迭代器类呢?为什么不能大大方方的直接使用节点指针呢?
原因是:虽然写的人麻烦了,但却能方便使用者,使用者直接对迭代器对象++就能完成遍历

我们接下来就要做这个事情了。

        Self& operator++(){_pNode = _pNode->_pNext;    //让节点指针指向下一个节点    //返回迭代器对象return *this;}
迭代器对象和指向节点的指针之间有以下关系: 迭代器对象封装了指向节点的指针,提供了一组操作接口,使得我们可以通过迭代器对象来访问节点中的元素。 迭代器对象可以通过指针的方式来访问节点中的元素,可以使用指针运算符来获取元素的值,或者使用指针递增/递减来移动到下一个/上一个节点。 迭代器对象可以隐藏底层容器的具体实现细节,使得我们可以通过统一的接口来访问不同类型的容器,提高了代码的可复用性和灵活性。

整体结构

首先先让你熟悉一下整个结构

	//节点类template<class T>struct ListNode{}//迭代器类template<class T, class Ref, class Ptr>class ListIterator{//...//利用结构体指针对节点结构体进行管理PNode _pNode;   //成员变量:结构体指针};//list类template<class T>class list{private:		PNode _pHead;	//哨兵位头节点(结构体指针)size_t _size;	//节点个数}

节点类

定义了一个节点结构体,在链表类中,我们需要能够直接访问节点的指针 _pPre 和 _pNext,以及节点的值 _val。如果将节点类定义为class,则需要在节点类中将这些成员变量和成员函数声明为public,或者在链表类中添加友元关系,以便链表类可以访问和操作节点的私有成员。
  template<class T>struct ListNode{//构造函数ListNode(const T& val = T()): _val(val) //节点的值, _pPre(nullptr), _pNext(nullptr){}ListNode<T>* _pPre; //节点的前驱ListNode<T>* _pNext; //节点的后继T _val;};

迭代器类

定义一个ListIterator类,在这个迭代器类中使用节点的指针,底层还是使用节点指针,迭代器类可以看作对节点类的封装,它提供了对节点的操作和访问的接口,使得用户可以通过迭代器来遍历和操作链表中的节点。 这里有一个点需要注意一下 这里的Ref指的是reference(引用), Ptr指的是pointer(指针),当然这里用T&、T*替换Ref和Ptr也是可以的 我们可以看到实例化部分,T&就传给了Ref,T * 就传给了Ptr

在这里插入图片描述
在这里插入图片描述
那么我们为什么要多传递两个模板参数呢?原因这两个模板参数可以助我们区分const迭代器类和普通迭代器类
在这里插入图片描述

值得一提的是重载->
当我们定义一个AA类型的对象的时候,并传入二元的数据,但是<<是不支持的(cout << *it << endl;) 我们可以想到用 cout << (*it)._a1 << " " << (*it)._a2 << endl, 这当然可以
但是迭代器模拟的是指针的行为,用->似乎更恰当一些,于是我们就重载了->运算符
operator->() 返回了 &_pNode->_val,这是什么意思呢?
&_pNode->_val这是一个指向AA类型数据的指针(AA *),对象指针再加上一个->就可以访问成员变量啦
cout << it.operator->() ->_a1 << " " << it.operator->()->_a2 << endl;

    struct AA{AA(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;};void test_list4(){Fan::list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));Fan::list<AA>::iterator it = lt.begin();while (it != lt.end()){//AA对象不支持流插入//error:cout << *it << endl;//cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;//cout << it.operator->() ->_a1 << " " << it.operator->()->_a2 << endl;it++;}cout << endl;}``````ctemplate<class T, class Ref, class Ptr>class ListIterator{//typedef结点类typedef ListNode<T>* PNode;//typedef迭代器类typedef ListIterator<T, Ref, Ptr> Self;public://构造函数ListIterator(PNode pNode = nullptr):_pNode(pNode) //初始化指向节点的指针{}//拷贝构造函数//l2(l1)ListIterator(const Self& l){_pNode = l._pNode;  }//重载*//从泛型的角度来说我们并不知道operator*()的返回值是什么Ref operator*(){return _pNode->_val;   //节点指针指向的值}//重载->(为自定义类型的数据准备)Ptr operator->(){return &_pNode->_val;   }//重载++Self& operator++(){_pNode = _pNode->_pNext;    //让节点指针指向下一个节点    //返回迭代器对象return *this;}//重载后置++Self operator++(int){Self tmp(*this);    //保存当前迭代器对象(当前指针指向的节点)_pNode = _pNode->_pNext;    return tmp;         //返回修改前的迭代器对象}//重载--Self& operator--(){_pNode = _pNode->_pPre; //让节点指针指向前一个节点//返回迭代器对象return *this;}//重载后置--Self& operator--(int){Self tmp(*this);    //保存当前迭代器对象(当前指针指向的节点)_pNode = _pNode->_pPre;return tmp;         //返回修改前的迭代器对象}//重载!=bool operator!=(const Self& l){return _pNode != l._pNode;  //判断节点指针是否相等}//重载==bool operator==(const Self& l){return _pNode == l._pNode;  //判断节点指针是否相等}PNode _pNode;   //成员变量:节点指针};

list类

实例化类模板

用typedef重命名,隐藏底层的细节,提供统一的访问修改方式

 //list类template<class T>class list{//实例化节点类模板typedef ListNode<T> Node;   typedef Node* PNode;        //重命名Node*指针类型public://实例化类模板typedef ListIterator<T, T&, T*> iterator;   //实例化const版本typedef ListIterator<T, const T&, const T&> const_iterator;
默认成员函数
// List的构造list(){//初始化头节点CreateHead();}//n个value的构造list(int n, const T& value = T()){CreateHead();  while (n--){push_back(value);   //尾插}}template <class Iterator>//一段迭代器空间的构造list(Iterator first, Iterator last){CreateHead(); while (first != last){push_back(*first);  //尾插    first++;}}//拷贝构造//lt2(lt1)list(const list<T>& l){CreateHead();   for (auto e : l){push_back(e);}}//赋值重载//lt2 = lt1list<T>& operator=(list<T> l){clear();    //清除数据(不包括头节点)for (auto e : l){push_back(e);}return *this;}//析构函数~list(){clear();//释放头节点delete _pHead;_pHead = nullptr;}
获取指向头尾节点的指针
 // List Iterator//返回第一个节点的指针(不是头节点)iterator begin(){//return iterator(_pHead->_pNext);return _pHead->_pNext;}//返回最后一个节点的下一个位置的节点指针iterator end(){//return iterator(_pHead)return _pHead;}//const:返回第一个节点的指针(不是头节点)const_iterator begin()  const{return const_iterator(_pHead->_pNext);//return _pHead->_pNext;  }//const://返回最后一个节点的下一个位置的节点指针const_iterator end()  const{return const_iterator(_pHead);//return _pHead;}
容量
        // List Capacitysize_t size(){return _size;}bool empty()const{return _size == 0;}
获取头尾结点的值
//返回第一个节点的值(不是头节点)T& front(){return _pHead->_pNext->_val;}//const版本:返回第一个节点的值(不是头节点)const T& front()const{return _pHead->_pNext->_val;}//返回最后一个节点的值(注意与end()区别)T& back(){return _pHead->_pPre->_val;}//const版本:返回最后一个节点的值const T& back()const{return _pHead->_pPre->_val;}
修改
  // List Modifyvoid push_back(const T& val){insert(end(), val); //复用}void pop_back(){erase(--end()); //复用}void push_front(const T& val){insert(begin(), val); //复用}void pop_front(){erase(begin()); //复用}// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){++_size;//结构体指针PNode cur = pos._pNode;PNode prev = cur->_pPre;PNode newnode = new Node(val);prev->_pNext = newnode;cur->_pPre = newnode;newnode->_pPre = prev;newnode->_pNext = cur;//返回新节点位置处的迭代器return iterator(newnode);}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){--_size;PNode cur = pos._pNode;PNode prev = cur->_pPre;PNode next = cur->_pNext;prev->_pNext = next;next->_pPre = prev;delete cur;return iterator(next);}

测试

void test_list1()
{Fan::list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);Fan::list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;
}
void test_list2()
{Fan::list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);lt1.push_back(5);Fan::list<int>::iterator it1 = lt1.begin();while (it1 != lt1.end()){cout << *it1 << " ";++it1;}cout << endl;Fan::list<int> lt2(lt1.begin(), lt1.end());Fan::list<int>::iterator it2 = lt2.begin();while (it2 != lt2.end()){cout << *it2 << " ";++it2;}cout << endl;Fan::list<int> lt3(lt2);Fan::list<int>::iterator it3 = lt3.begin();while (it3 != lt3.end()){cout << *it3 << " ";++it3;}
}
//打印const对象void print_list(const Fan::list<int>& lt){Fan::list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}}void test_list3(){Fan::list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(6);print_list(lt);}

小结

本篇文章对于初步接触模板的朋友们是有一定难度的,看完这篇文章,希望你能有所收获,在对模板的理解上更进一步,如果本文存在疏漏或错误的地方,还请您能够指出!

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

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

相关文章

Lua调用C#类

先创建一个Main脚本作为主入口&#xff0c;挂载到摄像机上 public class Main : MonoBehaviour {// Start is called before the first frame updatevoid Start(){LuaMgr.GetInstance().Init();LuaMgr.GetInstance().DoLuaFile("Main");}// Update is called once p…

关于SpringBoot2.x集成SpringSecurity+JJWT(0.7.0-->0.11.5)生成Token登录鉴权的问题

项目场景&#xff1a; 问题&#xff1a;遵循版本稳定的前提下&#xff0c;搭建权限认证框架&#xff0c;基于SpringBoot2.xSpringSecurity向上依赖jjwt0.7.0构建用户认证鉴权&#xff0c;起因是某L觉得jjwt0.7.0版本&#xff0c;官方已经放弃维护&#xff0c;且从maven仓库对0…

python二次开发CATIA:测量曲线长度

以下代码是使用Python语言通过win32com库来控制CATIA应用程序的一个示例。主要步骤包括创建一个新的Part文件&#xff0c;然后在其中创建一个新的几何图形集&#xff0c;并在这个集合中创建一个样条线。这个样条线是通过一组给定的坐标点来创建的&#xff0c;这些点被添加到集合…

SpringCloud-Config

一、介绍 &#xff08;1&#xff09;服务注册中心 &#xff08;2&#xff09;管理各个服务上的application.yml&#xff0c;支持动态修改&#xff0c;但不会影响客户端配置 &#xff08;3&#xff09;一般将application.yml文件放在git上&#xff0c;客户端通过http/https方式…

MyLife - Docker安装rabbitmq

Docker安装rabbitmq 个人觉得像rabbitmq之类的基础设施在线上环境直接物理机安装使用可能会好些。但是在开发测试环境用docker容器还是比较方便的。这里学习下docker安装rabbitmq使用。 1. rabbitmq 镜像库地址 rabbitmq 镜像库地址&#xff1a;https://hub.docker.com/_/rabbi…

介绍一款小巧的Excel比对工具-DiffExcel

【缘起&#xff1a;此前找了一通&#xff0c;没有找到免费又好用的Excel比对工具&#xff0c;而ExcelBDD需要把Excel文件存放到Git&#xff0c;因此迫切需要Excel比对工具。 最新升级到V1.3.3&#xff0c;因为git diff有变化&#xff0c;原来是git diff会修改文件名&#xff0…

Compose 组件 - 分页器 HorizontalPager、VerticalPager

一、概念 类似于 ViewPager&#xff0c;1.4 版本之前需要借助 accompanis 库&#xff0c;底层基于 LazyColumn、LazyRow 实现&#xff0c;在使用上也基本相同。默认情况下 HorizontalPager 占据屏幕的整个宽度&#xff0c;VerticalPager 会占据整个高度。 fun HorizontalPager(…

xshell使用方法(超详细)

一、安装 下载最新版安装即可&#xff0c;不需要做任何配置。 安装完成后输入账号名和邮箱&#xff0c;确认后邮箱会收到一条确认邮件&#xff0c;将里面的链接点开即可免费使用&#xff08;仅安装后会出现&#xff0c;认证后以后再打开不需要重复操作&#xff0c;如果重新安…

【MySQL】索引的作用及知识储备

为什么要有索引 索引可以提高数据库的性能。不用加内存&#xff0c;不用改程序&#xff0c;不用调sql&#xff0c;只要执行正确的create indix&#xff0c;查询的速度就可能提高成百上千倍。但相应的代价是&#xff0c;插入&#xff0c;更新&#xff0c;删除的速度有所减弱。 …

[论文分享] EnBinDiff: Identifying Data-Only Patches for Binaries

EnBinDiff: Identifying Data-Only Patches for Binaries [TDSC 2021] 在本文中&#xff0c;我们将重点介绍纯数据补丁&#xff0c;这是一种不会引起任何结构更改的特定类型的安全补丁。作为导致假阴性的最重要原因之一&#xff0c;纯数据补丁成为影响所有最先进的二进制差分方…

切换npm的版本

1、在配置环境变量的地址中&#xff0c;多准备几个已解压版本的node 2、要想升降版本直接更改该文件中的文件夹名称就行 环境变量中的path的值是不用变的C:\Program Files\nodejs

Ubuntu22安装Docker engine(apt安装方式)

一、准备工作 新创建一个虚拟机。 进入虚拟机&#xff1a; 二、安装docker docker现在对用不同主机提供了不同安装包&#xff1a;docker engine 和 docker desktop。 docker desktop适用于图形化的桌面电脑&#xff0c;docker engine适用于服务器。我们这里当然是安装docker…

SpringCloud-Gateway

一、介绍 &#xff08;1&#xff09;网关服务 &#xff08;2&#xff09;功能&#xff1a;断言、路由、过滤 &#xff08;3&#xff09;能避免用户直接访问到业务主机 二、项目搭建 a、编写pom.xml&#xff08;注意移除web框架&#xff0c;gateway中自带有&#xff09; &l…

7.定时器

定时器资源 CC2530有四个定时器TIM1~TIM4和休眠定时器 TIM1 定时器1 是一个独立的16 位定时器&#xff0c;支持典型的定时/计数功能&#xff0c;比如输入捕获&#xff0c;输出比较和PWM 功能。定时器有五个独立的捕获/比较通道。每个通道定时器使用一个I/O 引脚。定时器用于…

【API篇】二、源算子API

文章目录 0、demo数据1、源算子Source2、从集合中读取数据3、从文件中读取4、从Socket读取5、从Kafka读取6、从数据生成器读取数据7、Flink支持的数据类型8、Flink的类型提示&#xff08;Type Hints&#xff09; 0、demo数据 准备一个实体类WaterSensor&#xff1a; Data All…

【入门】.Net Core 6 WebApi 项目搭建

一、创建项目 1.1.创建新项目&#xff1a;打开开发工具>创建新项目>搜索API>选择C#语言的ASP.NET Core Web API 1.2.配置新项目&#xff1a;**自定义项目信息以及存储路径 1.3.其他信息&#xff1a;这里框架必须选择.NET 6.0,其他配置默认勾选即可&#xff0c;也可以根…

逐字稿 | 对比学习论文综述【论文精读】

对比学习在计算机视觉领域的发展历程&#xff0c;4个阶段&#xff1a; 百花齐放&#xff1a;方法、模型、目标函数、代理任务都还没有统一。CV双雄&#xff1a;MOCOv1、SimCLRv1、MOCOv2、SimCLRv2、CPC和CMC的延伸工作、SwaV&#xff0c;这个阶段发展非常迅速&#xff0c;以上…

云上攻防-云原生篇Docker安全系统内核版本漏洞CDK自动利用容器逃逸

文章目录 云原生-Docker安全-容器逃逸&内核漏洞云原生-Docker安全-容器逃逸&版本漏洞-CVE-2019-5736 runC容器逃逸-CVE-2020-15257 containerd逃逸 云原生-Docker安全-容器逃逸&CDK自动化 云原生-Docker安全-容器逃逸&内核漏洞 细节部分在权限提升章节会详解&…

SQLite4Unity3d安卓 在手机上创建sqlite失败解决

总结 要在Unity上运行一次出现库&#xff0c;再打包进APK内 问题 使用示例代码的创建库 var dbPath string.Format("Assets/StreamingAssets/{0}", DatabaseName); #else// check if file exists in Application.persistentDataPathvar filepath string.Format…

idea插件开发javax.net.ssl.SSLException: No PSK available. Unable to resume.

idea插件开发,编译出错 javax.net.ssl.SSLException: No PSK available. Unable to resume.at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:129)at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)at java.base/sun.security.ssl.…