【C++STL】双向循环链表与其迭代器的深度剖析及实现(百字短文速通)

news/2025/2/28 6:34:51/

1,双向循环链表基本结构的实现(不包含需要迭代器的部分)

先用struct封装链表的节点,这里我们仅需要提供一个构造函数即可,并且构造函数必须提供缺省值,因为会有如下使用场景:new Node();此时需要默认构造函数,所以必须有缺省值。

在list类模版中,先typedef 一下,这样方便使用Node<T>,

成员变量是双向循环链表的哨兵位头节点。

接下来是一些基本的接口函数:

空构造:仅仅需要new一个哨兵位头节点出来,但是要记得保证双向循环结构

push_back:传入一个T,不过T也有可能是自定义类型,所以要用&,const可以接收const对象和非const对象。

迭代器区间构造:

构造函数:构造时填入n个x。

值得一提的是,这个构造函数有可能跟上面的迭代器区间构造发生冲突,比如说:

list<int> lt2(5,1); 中5和1一般会被编译器默认为是int类型,于是:

对于迭代器区间构造函数:InputIterator会被推演为int,这样的话两个形参的类型都是int。

对于本构造函数:模版参数推演之后,两个参数分别是size_t和int。

系统认为两个int形参类型更加匹配,所以会去调用迭代器区间构造。

而迭代器区间的这个操作:

*first相当于对int进行解引用,所以会报错:非法的间接寻址

箭头所指处也更能证明我们的想法。

而解决方法只有重载一个如下图的函数:

这样就会调用这个函数,而不是迭代器区间的构造函数。

2,迭代器的实现以及相关函数的补充

我们知道list的底层空间不是连续的,所以原生指针的++和--不能达到我们预想的结果,我们所需要的是:++的时候走到它的next,--的时候走到它的prev。

将迭代器实现成一个类模版,对++ --以及一些其他的运算符进行重载可以解决这个问题。

2,1迭代器的实现

基本结构:

typedef是为了让代码更简洁易懂

成员变量是一个节点的指针

至于除了T之外的两个模版参数Ref和Ptr ,接下来会进行解释捏

构造函数:

++重载:

--重载:

判断是否为同一个节点:

重载*(解引用):

这里的返回值为什么不直接写成T&呢?

因为除了普通的迭代器,我们还要使用const迭代器,那样的话,返回值就是const T& 了,而const迭代器跟普通迭代器的结构极其相似,只有返回值不同,所以为了防止代码冗余,尽量复用代码,这才引入了模版参数Ref来手动控制。

重载->:Ptr模版参数的引入同上,是为了手动控制T和T*

你没看错,->的重载就是这么奇怪,这个需要我们以特殊的眼光来看待:

迭代器定义operator->的原因是:

迭代器是像指针一样的东西,如果是原生指针,解引用就可以获得对象,如果是一个结构,想要得到结构里的成员变量,就需要通过->来获得,比如日期类:

所有类型重载->都要这样,算是个奇怪的模版吧,至少我觉得很奇怪。

2,2相关函数的补充

看,这里我们手动传模版参数就可以实现iterator和const_iterator

begin,end,cbegin,cend:

erase:

clear:复用erase

析构函数:复用clear();

拷贝构造和赋值运算符重载:

这里很简单,唯独有一点要说的是,

拷贝构造函数是借助构造函数和std::swap来实现的,swap后,tmp对象会调用析构函数自动析构,

考虑到这一点,我们必须对this指向的对象进行处理,要不然就会把一个野指针交换给tmp,析构的时候会出现内存问题的。

在此之前,我们的处理都是将类中的指针类型的成员变量置为nullptr,

比如string:

比如vector:

但是这次不一样了,因为析构函数的内部必然需要对链表进行遍历,一边遍历一边释放节点,而遍历就需要用到iterator,iterator一旦++就会对当前节点进行解引用找到下一个节点,如果将_head置空,就会对空指针进行解引用。

所以我们要搞一个最简单的链表结构——空链表,以便于析构。

insert:

返回新插入节点的迭代器是为了符合官方库,不必在意这里。

3,反向迭代器的实现以及相关函数的补充

3,1反向迭代器的实现

Iterator这个模版参数,传的是谁的迭代器,实现的就是谁的反向迭代器,至于Ref和Ptr,

跟上面解释的一样。

成员变量就是某个容器的正向迭代器

构造函数:

重载*:

没错,解引用反向迭代器返回的是前一个位置的数据,这是因为下图:

由于rbegin和rend的位置跟begin和end的位置对称,所以,如果解引用返回当前节点的数据的话,就会访问到_head中的随机值,必须返回前一个位置的数据才行。

重载->:

重载++--

判断是否为同一个节点:

3,2相关函数的补充

这里是要先typedef出const_reverse_iterator的,因为如果先typedef出reverse_iterator的话,下面就会出现歧义。

ps:拷贝构造函数往往要对成员变量进行初始化,以便于交换之后析构。


http://www.ppmy.cn/news/23271.html

相关文章

SpringBoot + jackson + redis 序列化、反序列化 配置正确姿势

文章目录 1.背景2. 原来项目配置3.修改后配置4.正确配置5.小结1.背景 最近项目上 使用 SpringBoot 2.7.7 + jackson + redis 框架实现将javaBean 序列化和反序列化到 redis 中。但是最近在做登陆的时候将LoginUser 序列化到redis 中没问题,不重启服务的话反序列化成对象也没有…

Spring Boot整合Redis笔记

文章目录前言Java 操作 RedisJedis 操作-测试Jedis 实例-手机验证码Redis与Spring Boot整合整合步骤Redis 的事务操作Redis的事务定义Multi、Exec、discard 基本命令事务冲突的问题为什么要做成事务悲观锁乐观锁WATCH key [key ... ]Redis事务三特性Redis事务秒杀案例解决计数器…

Prometheus简介和部署

Prometheus简介 prometheus官方网站&#xff1a;https://prometheus.io/ prometheus是基于Go语言开发的一套监控、告警和时序数据库的组合&#xff0c;CNCF基金会的第二个毕业项目&#xff0c;在容器和微服务领域有着广泛的应用。一般情况下&#xff0c;是监控Kubernetes的标…

QT入门Input Widgets之QComboBox

目录 一、界面布局功能 1、界面位置介绍 2、界面常用操作属性 2.1基本属性 2.2添加子项目 二、属性功能介绍 1、代码添加item 2、批量插入 3、设置当前显示的索引 4、清除掉所有item 5、 切换item获得索引值与当前文本 三、Demo展示 此文为作者原创&#xff0c;转…

机器学习-基于KNN和LMKNN的心脏病预测

一、简介和环境准备 knn一般指邻近算法。 邻近算法&#xff0c;或者说K最邻近&#xff08;KNN&#xff0c;K-NearestNeighbor&#xff09;分类算法是数据挖掘分类技术中最简单的方法之一。而lmknn是局部均值k最近邻分类算法。 本次实验环境需要用的是Google Colab和Google Dr…

Linux系统安全:安全技术和防火墙

目录 一、安全技术 1、安全技术 2、防火墙分类 二、防火墙 1、iptables五表五链 2、黑白名单 3、iptables基本语法 4、iptables选项 5、控制类型 6、隐藏扩展模块 7、显示扩展模块 8、iptables规则保存 9、自定义链使用 一、安全技术 1、安全技术 ①入侵检测系统…

(十一)椭圆曲线密码(ECC)

椭圆曲线密码&#xff08;ECC&#xff09;ECC比RSA和经典的Diffie-Hellman等更强大、更高效——含256比特密钥的ECC比含4096比特密钥的RSA提供的安全性更强&#xff0c;但同时ECC也更复杂。ECC会对大数进行乘法运算&#xff0c;ECC做大数乘法是为了将所有元素看作点&#xff0c…

java顺序存储二叉树应用实例

八大排序算法中的堆排序&#xff0c;就会使用到顺序存储二叉树。 1.线索化二叉树 1.1先看一个问题 将数列 {1, 3, 6, 8, 10, 14 } 构建成一颗二叉树. n17 问题分析: 当我们对上面的二叉树进行中序遍历时&#xff0c;数列为 {8, 3, 10, 1, 6, 14 } 但是 6, 8, 10, 14 这几个…