C++——list类及其模拟实现

前言:这篇文章我们继续进行C++容器类的分享——list也就是数据结构中的链表,而且是带头双向循环链表


一.基本框架

namespace Mylist
{template<class T>//定义节点struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const <T>& x = T()):_next(nullptr)	,_prev(nullptr),_data(x){}};template<class T>class list{typedef ListNode<T> Node;public://构造函数list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}//析构函数~list(){iterator it = begin();while (it != end()){it = erase(it);}delete _head;_head = nullptr;}//数据个数size_t size(){iterator it = begin();size_t Size = 0;while (it != end()){Size++;it++;}return Size;}private:Node* _head;};
}

由于要满足存储任意类型的数据,所以我们必须要使用模版来进行定义。 


迭代器

关于list类中的最难之处,就是迭代器了。

因为迭代器的原理即为指针,对于string和vector这种创建的对象的物理空间是连续的类来说,我们可以直接对迭代器进行“++”、“--”等数学运算

而对于本质为链表的list来说,由于每个节点的物理空间都是随机创建,各个节点的地址并不连续,所以我们没法直接进行迭代器的数学运算,而需要对迭代器的各种功能进行重新定义,所以我们创建一个专门为迭代器服务的类

	//迭代器template<class T>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T> Self;ListIterator(Node* node):_node(node){}//解引用T& operator*(){return _node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}//后置++Self operator++(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& it){return _node != it._node;}//相等bool operator==(const Self& it){return _node == it._node;}Node* _node;};

随后在list类中将该类名重定义为iterator,便可正常使用迭代器了

		typedef ListIterator<T> iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}

 这里值得注意的是,因为是带头双向循环链表,所以链表的开始即哨兵位的下一个,而结尾就是哨兵位


但是现在的迭代器是存在问题的,它并不能实现对const修饰的数据的操作,所以我们还需要一个const迭代器。因为我们的普通迭代器就是用模版来实现的,所以这里可以直接通过模版来实现const迭代器

	//迭代器template<class T,class Ref>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T,Ref> Self;//构造函数ListIterator(Node* node):_node(node){}//解引用Ref operator*(){return _node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}//后置++Self operator++(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& it){return _node != it._node;}//相等bool operator==(const Self& it){return _node == it._node;}Node* _node;};
		typedef ListIterator<T,T&> iterator;typedef ListIterator<T,const T&> const_iterator;

这里有一个细节,因为T同时还要服务于Node类,所以不能直接对其进行修改,而是另用一个模版参数。 

 因为const对象与非const对象最大的不同之处在于对数据的访问,所以定义一个名为Ref(引用)的模版参数,来对解引用运算符重载函数进行改造


二.常用操作

1.插入

先来看任意位置的插入需要传入某个位置的指针pos

		//pos前插入void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;}

 现在对于我们来说就是非常简单,测试如下:

这里有一个小细节,如果我们插入的位置是第一个节点之前,由于it迭代器的指向并未改变,所以如果进行遍历,他就不会遍历出我们新插入的数据,所以需要更新一下it。


有了pos位置的插入之后,就可以用它来扩展头插和尾插

		//尾插void push_back(const T& x){insert(end(), x);}//头插void push_front(const T& x){insert(begin, x);}

2.删除

我们同样先写出pos位置的删除

		//pos删除iterator erase(iterator pos){Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;next->_prev = prev;prev->_next = next;delete cur;return iterator(next);}

由于删除会导致迭代器成为野指针,所以我们要对其进行更新, 测试如下:


同样由其扩展出头删和尾删

		//头删void pop_front(){erase(begin());}//尾删void pop_back(){erase(--end());}

3.拷贝

和string和vector一样,list的拷贝也需要使用深拷贝,那么它的拷贝构造函数该怎么写?

同样是要开辟新的空间,需要一个自己的头结点,随后按照被拷贝的链表的数据进行尾插即可:

		//拷贝构造list(const list<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;for (auto& e : lt){push_back(e);}}

测试如下:


此外,还有“=”运算符重载的方法:

		//交换void swap(list<T>& it){std::swap(_head, it._head);}//=运算符重载list<T>& operator=(list<T> lt){swap(lt);return *this;}

这里仍然是巧妙的运用swap函数,因为lt是一个临时拷贝,有自己的空间和地址,所以直接让两者进行交换,lt在退出函数时即被销毁,而拷贝者则继承了它的地址空间,测试如下:


总结

关于list类的基本知识就分享到这里啦。

因为与string和vector都存在很多相似之处,所以建议将这三者放在一起学习。

喜欢本篇文章记得一键三连,下期再见!

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

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

相关文章

京东云16核64G云服务器租用优惠价格500元1个月、5168元一年,35M带宽

京东云16核64G云服务器租用优惠价格500元1个月、5168元一年&#xff0c;35M带宽&#xff0c;配置为&#xff1a;16C64G-450G SSD系统盘-35M带宽-8000G月流量 华北-北京&#xff0c;京东云活动页面 yunfuwuqiba.com/go/jd 活动链接打开如下图&#xff1a; 京东云16核64G云服务器…

算法四十天-删除排序链表中的重复元素

删除排序链表中的重复元素 题目要求 解题思路 一次遍历 由于给定的链表是排好序的&#xff0c;因此重复的元素在链表中的出现的位置是连续的&#xff0c;因此我们只需要对链表进行一次遍历&#xff0c;就可以删除重复的元素。 具体地&#xff0c;我们从指针cur指向链表的头节…

Netty学习 应用Demo之“自动回复”聊天业务

Netty实现自动回复步骤 主要分成五步 1、创建EventLoopGroup实现循环组 管理EventLoop线程 2、创建Bootstrap &#xff0c;Bootstrap对于服务端而言&#xff0c;先后设置其中的线程组group、通道channel、处理器handler、客户端通道对应的处理器childHandler 3、自定义服务器接…

C#操作MySQL从入门到精通(6)——对查询数据进行排序

前言 在和MySql数据库交互的过程中,查询数据是使用最频繁的操作,并且我们经常需要对查询到的数据进行排序后输出,比如我想查询1列数据的最小值,那么我可以将查询到的数据进行升序(从小到大)排列,然后取第一个数据就是最小值。本文详细介绍了对查询数据进行排序的各种操…

HarmonyOS4-Stage模型

Stage模型介绍【舞台模型】&#xff1a; Stage模型 应用配置文件 全局应用配置文件&#xff1a; 模块配置文件&#xff1a; Ability生命周期 页面及组件的生命周期&#xff1a; 启动模式&#xff1a; "launchType": "multiton" // 会重新建&#xff0c…

本地项目提交 Github

工具 GitIdeaGithub 账号 步骤 使用注册好的 Github 账号&#xff0c;登陆 Github&#xff1b; 创建 Repositories (存储库)&#xff0c;注意填写图上的红框标注&#xff1b; 创建完成之后&#xff0c;找到存储库的 ssh 地址或 https 地址&#xff0c;这取决于你自己的配置…

matlab:有限差分求解纳维尔(Navier)边界的双调和(Biharmonic)方程,边值为零

我们考虑如下形式的双调和方程的数值解 其中&#xff0c;Ω是欧氏空间中的多边形或多面体域&#xff0c;在其中&#xff0c;d为维度&#xff0c;具有分段利普希茨边界&#xff0c;满足内部锥条件&#xff0c;f(x) ∈ L2(Ω)是给定的函数&#xff0c;∆是标准的拉普拉斯算子。算…

javaScript手写专题——实现instanceof/call/apply/bind/new的过程/继承方式

目录 原型链相关 手写instanceof 实现一个_instance方法&#xff0c;判断对象obj是否是target的实例 测试 手写new的过程 实现一个myNew方法&#xff0c;接收一个构造函数以及构造函数的参数&#xff0c;返回构造函数创建的实例对象 测试myNew方法 手写类的继承 ES6&…

【单片机】PMS5003,PM2.5传感器数据读取处理

文章目录 传感器介绍数据处理解析pm2.5的代码帮助、问询 传感器介绍 PMS5003是一款基于激光散射原理的数字式通用颗粒物浓度传感器,可连续采集 并计算单位体积内空气中不同粒径的悬浮颗粒物个数,即颗粒物浓度分布,进而 换算成为质量浓度,并以通用数字接口形式输出。本传感器可…

3D Web轻量化引擎HOOPS Commuicator如何从整体装配中创建破碎的装配零件和XML?

前言 虽然可以从某些本机CAD格式&#xff08;其子组件驻留在单独的文件中&#xff0c;例如CATIA V5、Creo - Pro/E、NX或SolidWorks&#xff09;创建破碎装配&#xff0c;但无法从整体装配文件&#xff08;例如IFC、Revit&#xff09;创建或3DXML。 本文介绍了一个示例&#…

12.C++常用的算法_遍历算法

文章目录 遍历算法1. for_each()代码工程运行结果 2. transform()代码工程运行结果 3. find()代码工程运行结果 遍历算法 1. for_each() 有两种方式&#xff1a; 1.普通函数 2.仿函数 代码工程 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vect…

基于拉格朗日分布算法的电动汽车充放电调度MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 程序简介 该模型主要做的是基于拉格朗日分布算法的电动汽车充放电调度模型。利用蒙特卡洛模拟法模拟出电动汽车负荷曲线&#xff0c;并求解出无序充电功率曲线和有序充电曲线&#xff0c;该模型在电动汽车个…

【Linux 学习】进程优先级和命令行参数!

1. 什么是优先级? 指定进程获取某种资源&#xff08;CPU&#xff09;的先后顺序&#xff1b; Linux 中优先级数字越小&#xff0c;优先级越高&#xff1b; 1.1 优先级和权限的区别&#xff1f; 权限 &#xff1a; 能不能做 优先级&#xff1a; 已经能了&#xff0c;但是获…

RX8111CE支持电池供电设备实现多计算芯片的数据交互

随着电池技术的发展&#xff0c;其容量和质量得到了显著提高。在目前的电池供电设备中&#xff0c;常常也会将传统主处理器和协处理器的结构应用到其中&#xff0c;这种计算机结构的引入大幅度提高了电池供电设备的计算能力&#xff0c;但是对设计也提出了更高的要求。在时钟系…

【Linux】虚拟化技术docker搭建SuitoCRM系统及汉化

CRM系统 CRM&#xff08;Customer Relationship Management&#xff0c;客户关系管理&#xff09;系统是一种用于管理和优化企业与客户关系的软件工具。在商业竞争激烈的现代社会中&#xff0c;CRM系统已成为许多企业提高销售、增强客户满意度和实现持续增长的重要工具。 搭建…

FMEA风险分析中几个常用的模型——SunFMEA软件

FMEA风险分析是确保风险管理成效的重要环节之一。为了很好地实施风险分析&#xff0c;风险管理专家开发了许多模型帮助其顺利实施&#xff0c;这些模型包括&#xff1a;领结模型、风险指数模型、因果模型、安全栅分析模型等。今天SunFMEA软件系统和大家一起分享这几种常用得模型…

libVLC 提取视频帧使用QGraphicsView渲染

在前面章节中&#xff0c;我们讲解了如何使用QWidget渲染每一帧视频数据&#xff0c;这种方法对 CPU 负荷较高。 libVLC 提取视频帧使用QWidget渲染-CSDN博客 后面又讲解了使用OpenGL渲染每一帧视频数据&#xff0c;使用 OpenGL去绘制&#xff0c;利用 GPU 减轻 CPU 计算负荷…

【前端捉鬼记】使用nvm切换node版本后再用node -v查看仍然是原来的版本

今天遇到一个诡异的问题&#xff0c;使用nvm切换node版本&#xff0c;明明提示已经切换成功&#xff0c;可是再次查看node版本还是之前的&#xff01; 尝试了很多办法&#xff0c;比如重新打开一个cmd窗口、切换前执行nvm install version都没成功&#xff0c;直到找到这篇文章…

mapv修改源码实现图标和管道到统一页面显示,图标和管道和点击

一、效果图 二、背景 map 地图添加marker&#xff0c;是操作的dom&#xff0c;而mapv是使用的canvas方式&#xff0c;所以性能要好 三、Mapv和MapVGL的区别 百度地图 JavaScript API GL快速升级 和mapVGL的使用 Mapv 是一款基于百度地图的大数据可视化开源库&#xff0c;可以…

安卓的认证测试

1 CTS CTS 是 Android 兼容性测试套件&#xff0c;用于验证设备是否符合 Android 平台的兼容性标准。它包含一系列测试用例&#xff0c;涵盖了设备的各个方面&#xff0c;如硬件功能、软件功能、API 的正确实现等。通过 CTS 测试&#xff0c;设备厂商可以确保其设备符合 Andro…