[C++进阶[六]]list的相关接口模拟实现

1.前言

本章重点

在list模拟实现的过程中,主要是感受list的迭代器的相关实现,这是本节的重点和难点。

 2.list接口的大致框架

 list是一个双向循环链表,所以在实现list之前,要先构建一个节点类

template <class T>
struct ListNode
{T _val;ListNode* _prev;ListNode* _next;ListNode(const T& val = T())//构造函数:_prev(nullptr), _next(nullptr),_val(val){}
};

节点中存储一个T模板类型的值和
上一个节点的地址和下一个节点的地址

在List类中,由于链表都是些链接关系,所以List类中的成员变量只需要定义一个那就是头节点head,知道head的链接关系。就能够知道list类对象中存放的内容!

template <class T>
class List
{public:typedef ListNode<T> Node;private://带头节点的无参构造一个随机值void CreateHead(){_pnode = new Node;_pnode->_prev = _pnode;_pnode->_next = _pnode;}Node* _pnode;
};

解释:给头节点pnode开辟一份空间后,头节点的指向最开始都是指向自身:

3.list的构造和析构函数

构造函数

此处介绍三种构造函数:空构造;构造n个值为val的节点;用迭代器区间来进行构造

无参构造:

//构造函数1
List()
{CreateHead();
}

构造n个值为val的节点

//构造函数2:创建n个值为val的节点
List(int n, const T& val = T())
{CreateHead();for (int i = 0; i < n; i++){push_back(val);}
}

这个地方使用了push_back先用着,后续再实现

迭代器区间来进行构造:

//构造函数3:迭代器区间来进行构造
template <class InputerIterator>
List(InputerIterator first, InputerIterator last)
{CreateHead();while (first != last){push_back(*first);first++;}
}

ps:注意,这里不管是什么构造,在最开始时都需要构造一个带哨兵卫的头节点,所以说都要先使用CreateHead函数来进行构建。

析构函数

由于链表是一块一块的空间,通过某种形式把他连接起来。所以在析构函数时,要先把这些空间全部释放掉,然后再删除带哨兵卫的头结点。

//析构函数
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}
~List()
{clear();delete _pnode;_pnode = nullptr;
}

这里使用到了迭代器来进行遍历,后续会实现迭代器相关的功能。

4.拷贝构造函数和赋值重载函数

拷贝构造函数

直接使用简单快速的写法来完成深拷贝。

	//拷贝构造函数List(const List<T>& node){CreateHead();List<T> tmp(node.begin(), node.end());Swap(tmp);}void Swap(List<T>& node){swap(_pnode,node._pnode);}

赋值重载函数

	//赋值重载函数List<T>& operator=(const List<T>& node){List<T>tmp(node.begin(), node.end());Swap(tmp);return *this;}

这样的写法有两个精妙之处:

1.它先定义一个临时变量tmp来接受node的所有值,然后再将临时变量tmp
的pnode和拷贝的pnode交换,这样一来就完成了拷贝构造函数,并且tmp变量是构造函数初始化的,它是深拷贝,所以lt2对于lt1也是深拷贝。

2.tmp是临时变量,除了作用域会销毁,也就是出了此拷贝构造函数后会销毁,销毁时会调用析构函数,然而要构造的pnode以及和tmp的pnode交换了,所以tmp销毁时实际上是在帮原先的要构造的pnode销毁内存!

5.与容量有关的函数

size和empty

bool empty()
{return size() == 0;
}size_t size()
{size_t sz = 0;iterator it =begin();while (it!= end()){sz++;it++;}return sz;
}

6.迭代器的模拟实现

其实仔细分析下来,发现链表是无法进行*和++,--以及->这些相关操作的。那么想让链表和迭代器一样进行++,--,*和->这些相关的操作,那么只需要封装一个类,把这些函数进行重写就可以了。

迭代器的大体结构如下:

template<class T>
struct List_iterator 
{typedef ListNode<T>* PNode;typedef List_iterator<T> Self;PNode _node;
};

构造函数

List_iterator(const PNode& node=nullptr):_node(node)
{}

拷贝构造

//拷贝构造
List_iterator(const Self& l)
{_node=l._node;
}

ps:由于这里把迭代器相关类型重命名成了self,所以这里给出的就直接使用了self

++和--函数

Self& operator++()//前置++
{_node = _node->_next;return *this;
}
Self& operator--()//前置--
{_node = _node->_prev;return *this;
}
Self operator++(int) //后置++
{List_iterator<T> tmp(*this);_node = _node->_next;return tmp;
}
Self operator--(int) //后置--
{List_iterator<T> tmp(*this);_node = _node->_prev;return tmp;
}

==和!=函数

bool operator==(const Self& node)
{return _node == node._node;
}
bool operator!=(const Self& node)
{return _node != node._node;
}

解引用和箭头->函数

迭代器的使用就像指针一样,所以解引用后应该直接得到节点的数据!

	T& operator*(){return _node->_val;}T* operator->(){return &(_node->_val);}

解释:解引用大家肯定都能理解。那么对用箭头->函数理解起来可能就有难度了。

举个例子来帮助我们理解

当list容器当中的每个结点存储的不是内置类型,而是自定义类型,例如日期类,那么当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员:

	list<Date> lt;Date d1(2021, 8, 10);Date d2(1980, 4, 3);Date d3(1931, 6, 29);lt.push_back(d1);lt.push_back(d2);lt.push_back(d3);list<Date>::iterator pos = lt.begin();cout << pos->_year << endl; //输出第一个日期的年份

注意: 使用pos->_year这种访问方式时,需要将日期类的成员变量设置为公有。

对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可。

T* operator->()
{return &_pnode->_val; //返回结点指针所指结点的数据的地址
}

讲到这里,可能你会觉得不对,按照这种重载方式的话,这里使用迭代器访问日期类当中的成员变量时不是应该用两个->吗?

这里本来是应该有两个->的,第一个箭头是pos ->去调用重载的operator->返回Date* 的指针,第二个箭头是Date* 的指针去访问对象当中的成员变量_year。

但是一个地方出现两个箭头,程序的可读性太差了,所以编译器做了特殊识别处理,为了增加程序的可读性,省略了一个箭头。

7.插入删除相关函数

有了迭代器之后插入删除相关函数就好实现了。

插入函数insert

//在pos位置前插入值为val的节点
iterator insert(iterator pos,const T&val)
{//创建节点Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = pos._node->_prev;//开始插入prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}

这里我实现的是在插入一个值之后,并把这个值的的迭代器位置返回去。

erase函数

//删除pos节点,然后返回下一个节点
iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* next = pos._node->_next;Node* prev = pos._node->_prev;delete cur;prev->_next = next;next->_prev = prev;return iterator(next);
}

如果这里不返回一个合法的迭代器位置的话,那么就会有可能出现迭代器失效。

push_back和pop_back


push_back和pop_back函数分别用于list的尾插和尾删,在已经实现了insert和erase函数的情况下,我们可以通过复用函数来实现push_back和pop_back函数。

push_back函数就是在头结点前插入结点,而pop_back就是删除头结点的前一个结点。

	void push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}

8.总结

总的来说,list的底层实现较于vector来说要复杂一点,这其中的底层原因
就是list的迭代器还需要一层封装,而vector的迭代器不需要额外封装。但是在我们使用的角度来看,这两者并没有什么区别。

C++的强大就在于把复杂的底层全部封装起来了,而表面的使用上
list和vector并无太大区别,这就是C++封装的魅力!

 list模拟实现全部代码如下:

simulate_list/simulate_list · 青酒余成/初识数据结构 - 码云 - 开源中国 (gitee.com)

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

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

相关文章

Java 中 List 常用类和数据结构详解及案例示范

1. 引言 在 Java 开发中&#xff0c;List 是最常用的数据结构接口之一&#xff0c;它用于存储有序的元素集合&#xff0c;并允许通过索引进行随机访问。电商系统中&#xff0c;如购物车、订单列表和商品目录等功能都依赖 List 进行数据管理。选择适当的 List 实现类能够显著提…

C++STL~~priority_queue

文章目录 容器适配器一、priority_queue的概念二、priority_queue的使用三、priority_queue的练习四、仿函数五、总结 容器适配器 什么是适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该种模式是将…

交流电力控制电路之交流调功电路、交流电力电子开关

目录 一、交流调功电路 二、交流电力电子开关 交流调压电路可看&#xff1a;交流调压电路 交流调压电路、交流调功电路和交流电力开关的异同点&#xff1a; 一、交流调功电路 交流调功电路用于调节电力设备的功率输出&#xff0c;通过改变电路中电压、电流的有效值&#xff…

STL,智能指针和线程安全,线程安全的单例模式和懒汉饿汉的实现,以及读者写者问题

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f4da;STL&#xff0c;智能指针和线程安全 &#x1f4d5;STL中的容器是否是线程安全的?&#x1f4a1;智能指针是否是线程安全…

Threejs之看房案例(下)

本文目录 前言最终效果1、点精灵1.1 添加点精灵1.2 点精灵效果2、添加事件2.1 鼠标移动事件2.1.1 效果2.2 鼠标点击事件2.2.1 效果2.3 切换互通3. 完整代码前言 在Threejs之看房案例(上)这篇博客中我们已经完成了大厅的3d观看效果,但是我们会发现如果想去其他房间观看,没有…

使用SQL递归查询树状结构,又可以跟同事吹牛了!

前言 在关系型数据库中&#xff0c;数据通常存储为二维表格&#xff08;rows 和 columns&#xff09;。然而&#xff0c;在实际业务中&#xff0c;很多场景下我们需要处理树状结构的数据&#xff0c;例如&#xff1a; 公司组织架构&#xff1a;从某个部门开始&#xff0c;查询…

Python异常处理:自定义异常②

文章目录 1. 什么是自定义异常&#xff1f;2. 为什么需要自定义异常&#xff1f;3. 如何定义自定义异常&#xff1f;3.1 基本自定义异常3.2 带详细信息的自定义异常3.3 自定义异常的继承层次 4. 使用自定义异常4.1 抛出自定义异常4.2 捕获自定义异常 5. 自定义异常的应用场景5.…

二叉树——数据结构

这次我们来学习一下数据结构中的二叉树 1. 二叉树的概念及结构 1.1 二叉树的定义 定义&#xff1a;所有结点的度小于等于2的树。 上图中可以看出 二叉树不存在度大于2的结点二叉树的子树有左右之分&#xff0c;次序不能颠倒&#xff0c;因此二叉树是有序树。 任意二叉树都…

2024年适合培训服务企业的7款CRM盘点

培训服务行业在线索管理、客户管理、数据分析、项目管理、师资管理和课程管理等方面&#xff0c;使用CRM可以事半功倍&#xff0c;最重要的是&#xff0c;可以用数据说话&#xff0c;找到降本增效的方向。 下面对培训服务行业常用测CRM做个盘点&#xff0c;包括国内比较头部的…

米壳AI:跨境电商必备:不损失原图的图片翻译工具!

嘿&#xff0c;跨境电商的小伙伴们&#xff01; 今天来聊聊如何突破语言壁垒&#xff0c;让你的商品在国际市场上大放异彩。 随着 “一带一路” 战略的不断推进&#xff0c;跨境电商的发展势头愈发强劲。然而&#xff0c;语言障碍却成为了跨境交易中的一大难题。别担心&#x…

ppt组织结构图怎么增加分支?

在使用ppt里边的SmartArt来制作组织结构图的时候&#xff0c;我们发现里边的图形不够用&#xff0c;需要增加分支&#xff0c;这也就是大家近期问的ppt组织结构图怎么增加分支。今天设计学徒自学网小编就把具体的操作步骤分享给大家了&#xff0c;希望能帮助你们&#xff01; …

RFID技术实现消防物资消防车无感化智能管理设计方案

在消防工作中&#xff0c;物资管理的高效性与准确性直接关系到救援行动的成败&#xff0c;传统的消防物资管理方式主要依赖人工记录和定期盘点&#xff0c;这种方式存在着诸多弊端。首先&#xff0c;人工记录容易出现错误&#xff0c;数据的准确性难以保证。例如&#xff0c;在…

制作U盘安装操作系统(启动盘、系统盘、Windows、Linux)

第一种&#xff08;Windows&#xff09; 官网windows制作启动盘 1. 打开Win11下载官网 下载 Windows 11https://www.microsoft.com/zh-cn/software-download/windows11 2. 下载制作操作系统工具 这里不要下载错了 3. 启动工具 选择U盘&#xff0c;选择你的U盘即可&#xf…

TASK-CUSTOMIZEDMASKED AUTOENCODERVIA MIXTURE OF CLUSTER-CONDITIONAL EXPERTS

发表于&#xff1a;ICLR 2023 notable top 25%&#xff08;相当于spotlight) 推荐指数: #paper/⭐⭐⭐ 论文链接: Task-customized Masked Autoencoder via Mixture of Cluster-conditional Experts | OpenReview poster链接&#xff1a;ICLR 2023 Task-customized Masked Auto…

人类行为识别系统源码分享

人类行为识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

使用streaming-json-py插件处理JSON数据流:详细指南

目录 一、streaming-json-py简介 二、安装与配置 三、基本使用 示例1:处理不完整的JSON对象 示例2:处理不完整的JSON数组 四、高级用法 实时数据流分析 日志处理 五、性能优化与错误处理 六、总结与展望 在数据驱动的现代社会,实时处理数据流已成为许多应用和服务…

Linux·权限与工具-git与gdb

1. git工具 git是一款软件&#xff0c;发明它的人同时发明了Linux操作系统&#xff0c;也就是大名鼎鼎的Linus Torvalds 林纳斯托瓦兹。后来人们把git软件包装&#xff0c;产生了github、gitee等平台。 git产生的初衷就是便于进行多人协同管理&#xff0c;同时它还可以用来将本…

GB/T28181-2022相对老版本有哪些变动?

GB/T28181-2022新版概述 GB/T28181-2022是《公共安全视频监控联网系统信息传输、交换、控制技术要求》的国家标准&#xff0c;该标准在2022年12月30日发布&#xff0c;并于2023年7月1日正式实施。以下是关于GB/T28181-2022的详细解析&#xff1a; 一、标准概述 GB/T28181-20…

2024/9/18 模型的存储与读取

一、模型的存储与读取 主要涉及到torch.save和torch.load函数 新建两个python文件&#xff1a; 1.在model_save文件中保存模型(方式一)和模型参数(方式二) 2.在model_load文件中读取模型(方式一)和模型参数并装载模型(方式二)

海外绿色农业果蔬投资系统可以二开多语言

食品安全已经是全球非常重视&#xff0c;关于农业方面的基础建设投资都在大力推进&#xff0c;做一个绿色农业果蔬投资是一个非常不错的。希望这个系统能对你有很大的帮助&#xff01;