C++(17)——list的模拟实现

前面的文章中,介绍了stringvector的模拟实现,本篇文章将介绍对于list的模拟实现。

目录

1. list的基本结构:

2. list功能实现:尾部插入元素:

3. list迭代器的实现:

4. list功能实现:在任意位置前插入元素: 

4.1 函数实现方法:

4.2 函数运行逻辑:

5. list功能实现:删除任意位置的结点:

6. 拷贝构造与赋值重载:

7. list功能实现:clear与析构函数:


1. list的基本结构:

对于list,可以将其看作数据结构中的双向带头循环链表一起学数据结构(4)——带头结点的双向循环链表_带头结点的双循环链表-CSDN博客

针对于双向带头循环链表,其基本构成单元为如下图所示:

其中,prev用来保存上一个结点的地址,next用于保存下一个结点的地址,data用于保存数据。对于上述结构单元,可以由下方的代码表示:

namespace violent
{
	template<class T>
	struct ListNode
	{
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;
	};
}

对于双向带头循环链表,其结构可以由下图表示:

其中,链表的第一个结点称为哨兵位头结点,此结点的data不用于存储数据,只是利用prev,next建立其他结点的关系。因此,在编写针对于链表单元结构的构造函数时,需要考虑到哨兵位头结点。本文将采用隐式类型转换的方式,来完成对于构造函数的编写:

template<class T>
	struct ListNode
	{
		ListNode(const T& x = T())
			: _prev(nullptr)
			, _next(nullptr)
			, _data(x)
		{}
		
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;
	};

对于一个带头双向循环链表结构的实现,可以看作是若干个结构单元的相互链接,因此在初始化链表结构时,只需要在构造函数中,完成对于哨兵位头结点的建立,以及其内部指针的指向即可,即:

具体的实现方法,就是再创建一个类,名为list的类,将上述表示单个结点结构的类作为一种类型引入到list,即:

template<class T>
	class list
	{
		typedef ListNode<T>  Node;
		Node* _node;
	};

通过上述给出的图片,可以得到下面的构造函数:

class list
	{
		typedef ListNode<T>  Node;
	public:
		list()
		{
			_phead = new Node;
			_phead->_next = _phead;
			_phead->_prev = _phead;
		}
		Node* _phead;
	};

2. list功能实现:尾部插入元素:

在插入一个元素之前,首先需要创建一个新的结构单元用于保存这个元素,例如需要插入的元素为x,需要提前创建一个名为newnode的结点用于存储该元素,即:

Node* newnode = new Node(x);

newnode进行插入时,即:

第一步,首先获取链表最后一个结点的地址,这里命名为tail,通过上图不难得出taii=phead->prev.

第二步,建立newnodetail的联系,即:tail->next=newnode,newnode->prev=tail,对于此关系的图片表示是如下:

 最后一步:建立pheadnewnode的联系,即phead->prev=newnode,newnode->next=phead

代码实现如下:

void push_back(const T& x)
		{
			Node* newnode = new Node(x);
			Node* tail = _phead->_prev;
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _phead;
			_phead->_prev = newnode;
		}

3. list迭代器的实现:

       在vecotr,string中,由于这两种数据结构的空间是连续的,因此,在实现其迭代器功能时,通常先利用typedef对指针进行更名,使用时直接++即可。

      但是对于list,前面说到list的结构可以近似的看为链表,由于链表的空间不连续,因此,在使用迭代器进行访问时,对迭代器++不能达成访问空间中下一个结点的目的。对于list来说,正确访问下一个结点的访问为通过本结点的next获取下一个结点的地址。因此,可以考虑使用运算符重载,将++的运行逻辑从指向连续空间的下一个地址改为通过本结点的next访问下一个结点。

     但是,运算符重载只能针对于自定义类型,因为迭代器的实现是依托于指针来完成的。虽然在前面利用创建自定义类型的方式创建了链表中单个结点结构的对象,但是,需要注意,此处运算符重载进行重载的目标并不是ListNode这个自定义类型,而是这个类型的指针,而指针是一个内置类型,因此,不能直接完成对于指针的重载。而是再创建一个自定义类型用于封装指针,在内部进行运算符重载。

    对于封装方法:首先需要将表示单个结点结构的自定义类型引入到新的类中,此处将这个类命名为__list_iterator。为了方便使用,将表示单个结点结构的类重命名为Node,成员变量为类型为Node的指针。即:

template<class T>
		struct __list_iterator
		{
			typedef ListNode<T> Node;

			Node* _node;
		};

对于上述类的初始化如下:

template<class T>
		struct __list_iterator
		{
			typedef ListNode<T> Node;
			__list_iterator(Node* node)
				: _node(node)
			{}

			Node* _node;
		};

为了正常的使用迭代器来完成对于list的打印,不但需要对于++,还需要对于*!=进行重载,代码如下:

template<class T>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T> self;
		__list_iterator(Node* node)
			: _node(node)
		{}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self& operator++(int)
		{
			self tmp(_node);
			_node = _node->next;
			return tmp;
		}

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

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		Node* _node;
	};

       在完成上述步骤后,向list中引入__list_iterator,再添加end,begin两个函数用于表示链表的起始和结束。

       需要注意的是,在定义链表的起始时,并不能定义成哨兵位头结点,因为哨兵位头结点并没有保存数据,在访问链表时,需要从第一个保存数据的结点开始访问。而对于尾结点,由于链表是双向循环的。因此,可以将不存储任意数据的哨兵位头结点看作尾结点,代码如下:

typedef __list_iterator<T> iterator;

		iterator begin()
		{
			return _phead->_next;
		}

		iterator end()
		{
			return _phead;
		}

在完成了上述步骤后,就可以使用迭代器对于list进行正常的访问,例如:

void test1()
	{
		list<int> It;
		It.push_back(1);
		It.push_back(2);
		It.push_back(3);
		It.push_back(4);
		list<int>::iterator it1 = It.begin();
		while (it1 != It.end())
		{
			cout << *it1 << ' ';
			++it1;
		}
	}

代码运行结果如下:

4. list功能实现:在任意位置前插入元素: 

4.1 函数实现方法:

与尾部插入元素的大致思路相同,首先需要创建一个结点来存储这个元素:

Node* newnode = new Node(x);

例如,需要在pos位置之前插入这个结点,首先需要获取pos位置的前一个结点的地址prev。但是,pos只是类型为iterator的一个对象,需要先创建一个变量cur,来存储pos中成员变量node,也就是这个结点的地址,通过cur来获取pos位置前一个结点的坐标,即:

Node* cur = pos._node;
Node* prev = cur->_prev;

在对 prev,newnode,cur这三个位置所代表的结点进行连接,即:

void insert(iterator pos,const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}

利用下方的代码对于insert函数功能进行测试,即:

It.insert(It.begin(), 5);
		It.insert(It.begin(), 6);
		It.insert(It.begin(), 7);
		It.insert(It.begin(), 8);
		
		for (auto e : It)
		{
			cout << e << ' ';
		}

运行结果如下:

4.2 函数运行逻辑:

为了更清晰的了解insert的动作逻辑,下面给出函数的整体运行步骤,例如对于下方的代码:

It.insert(It.begin(), 5);

函数运行的第一步为首先通过begin()函数获取地址:

在返回时,由于函数的返回类型为自定义类型iterator,因此在返回前会去调用__list_insertiterator中的构造函数,来构造一个临时变量作为返回值,即:

再获取返回值后,跳转到insert函数中,即:

 最后根据insert中编写好的代码的顺序进行运行。

在完成了对于insert函数的编写后,对于push_back函数可以复用insert,从而简化push_back,即:

void push_back(const T& x)
		{
			insert(_phead, x);
		}

同理也可以实现头部插入任意元素push_front,即:

void push_front(const T& x)
		{
			insert(_phead->_next, x);
		}

5. list功能实现:删除任意位置的结点:

在删除任意位置的结点前,首先需要找到这个结点的前结点和后结点的地址,为了方便表达,用prev表示前结点的地址,用next表示后结点的地址。代码如下:

iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

			prev->_next = next;
			next->_prev = prev;

			delete cur;
			return next;
		}

在完成了对于erase的编写后,可以通过复用erase来完成对于头部删除pop_front和尾部删除pop_back,代码如下:

void pop_back()
		{
			erase(_phead->_prev);
		}

		void pop_front()
		{
			erase(_phead->_next);
		}

利用下方代码对上述的函数进行测试:
 

It.pop_back();
		It.pop_back();
		It.pop_front();
		It.pop_front();
		for (auto e : It)
		{
			cout << e << ' ';
		}

运行结果如下:

6. 拷贝构造与赋值重载:

list(const list<T>& s)
		{
			empty();
			for (auto e : s)
			{
				push_back(e);
			}
		}

		void swap(list<T>& s)
		{
			std::swap(_phead, s._phead);
		}

		list<T>& operator=(list<T> s)
		{
			swap(s);
			return *this;
		}

7. list功能实现:clear与析构函数:

对于clear函数,其功能是用于清理空间中的所有内容,即所有开辟的结点,但是不包括哨兵位头结点。

对于析构函数,则是在clear函数的基础上,将哨兵位头结点也进行处理。

二者对应代码如下:

void clear()
		{
			iterator i2 = begin();
			while (i2 != end())
			{
				i2 = erase(i2);
			}
		}

		~list()
		{
			clear();

			delete _phead;
			phead = nullptr;
		}


 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue2 导入使用vue-codemirror详解

目录 vue2 导入使用vue-codemirror详解1 介绍2 安装使用2.1 安装 vue-codemirror2.2 使用 codemirror2.2.1 引入 3 配置详情3.1 语言模式配置3.2 自动高度设置3.4 主题配置 4 总结 vue2 导入使用vue-codemirror详解 1 介绍 vue-codemirror是一个基于Vue的代码在线编辑器组件&…

【jenkins】主从机制及添加Slave节点操作

一、master-slave 日常构建Jenkins任务中&#xff0c;会经常出现下面的情况&#xff1a; 自动化测试需要消耗大量的 CPU 和内存资源&#xff0c;如果服务器上还有其他的服务&#xff0c;可能会造成卡顿或者宕机这样的情况&#xff1b; Jenkins 平台上除了这个项目&#xff0c…

把成绩私发给家长

与家长保持及时、有效的沟通对于学生的成长至关重要。但有时候&#xff0c;我会选择将学生的成绩私发给家长&#xff0c;而不是在公共场合公布。这样做有以下几个原因。 保护学生的隐私。每个学生都拥有自己的个人信息&#xff0c;这包括学习成绩。在公共场合公布成绩&#xf…

Sqoop数据迁移工具

概述 Apache Sqoop&#xff08;SQL-to-Hadoop&#xff09;项目旨在协助RDBMS与Hadoop之间进行高效的大数据交流。用户可以在 Sqoop 的帮助下&#xff0c;轻松地把关系型数据库的数据导入到 Hadoop 与其相关的系统 (如HBase和Hive)中&#xff1b;同时也可以把数据从 Hadoop 系统…

【计算机二级考试C语言】C递归

目录 C 递归 数的阶乘 实例 斐波那契数列 实例 C 递归 递归指的是在函数的定义中使用函数自身的方法。 举个例子&#xff1a; 从前有座山&#xff0c;山里有座庙&#xff0c;庙里有个老和尚&#xff0c;正在给小和尚讲故事呢&#xff01;故事是什么呢&#xff1f;"从…

都 2024 年了!程序员的到底出路在哪里!?继续卷技术?晋升管理层?还是转业?

都 2024 年了&#xff01;程序员的到底出路在哪里&#xff01;&#xff1f;继续卷技术&#xff1f;晋升管理层&#xff1f;还是转业&#xff1f; 1&#xff09;程序员的难处2&#xff09;程序员专业方向3&#xff09;大数据3.1.大数据开发涉及到哪些技术3.2.大数据开发涉及到的…

VxTerm:SSH工具中的中文显示和乱码时的相关信息和一些基本的知识

当我们写的程序含有控制台(Console)输出时&#xff0c;如果输入内容包含中文时&#xff0c;我们一般需要知道下面的信息&#xff0c;才能正确的搞清楚怎么处理中文显示的问题&#xff1a; 1、实际程序或文件中的实际编码&#xff1a; Linux下的应用程序和文本文件&#xff0c;…

2024年最佳的免费UI设计工具推荐

随着用户界面设计行业的蓬勃发展&#xff0c;越来越多的设计师加入到用户界面设计中来。选择一个方便的用户界面设计工具尤为重要&#xff01;除了传统的用户界面设计工具外&#xff0c;在线用户界面设计工具也受到越来越多设计师的青睐。这种不受时间、地点和计算机配置限制的…

centos7.6安装Docker详细步骤(无坑版教程)

一、安装前必读 在安装 Docker 之前&#xff0c;先说一下配置&#xff0c;我这里是Centos7 Linux 内核&#xff1a;官方建议 3.10 以上&#xff0c;3.8以上貌似也可。 注意&#xff1a;本文的命令使用的是 root 用户登录执行&#xff0c;不是 root 的话所有命令前面要加 sudo…

uniapp 使用canvas 画海报,有手粘贴即可用(拆成组件了,看后面)

1.直接使用 html部分 <view click"doposter">下载海报</view> <canvas canvas-id"myCanvas" type2d style"width: 370px; height: 550px;opcity:0;position: fixed;z-index:-1;" id"myCanvas" />js 部分 drawBac…

【数据结构】(三)树Tree

目录 1、基本概念 2、二叉树Binary Tree 3、树、森林与二叉树的转换 4、赫夫曼树Huffman Tree与赫夫曼编码Huffman Coding 1、基本概念 &#xff08;1&#xff09;树&#xff08;Tree&#xff09;是 n&#xff08;n ≥\geq 1&#xff09;个节点的有限集&#xff0c;n 0时称…

JavaScript基础(一)旧版基础笔记总结

开新藩&#xff08;虽然博主早以前已经学过了&#xff09;&#xff0c;从0开始复习JS&#xff0c;一方面应对毕设&#xff0c;一方面后期可能找找实习&#xff0c;一方面复试可能也会涉及到吧&#xff0c;说起这个最近越等越焦虑QAQ&#xff0c;还要一个月才出分呢...... 本帖先…

HubSpot CRM是什么?有什么功能和特点?

HubSpot CRM&#xff08;Customer Relationship Management&#xff0c;客户关系管理&#xff09;是一款由HubSpot公司开发的免费的、云端的CRM软件。HubSpot CRM致力于帮助企业更好地管理客户关系&#xff0c;提高销售效率&#xff0c;同时通过集成多个营销、销售和服务工具&a…

springboot mybatis-plus 项目分层笔记

整体定义 config: 配置项&#xff0c;包含configuration注解 constants: 常量类enums: 枚举 exceptions: 全局异常处理&#xff0c;自定义异常&#xff0c;RestControllerAdvice 注解 fia3: 三大器依据执行顺序&#xff1a;过滤器filter、拦截器interceptor、切面aop 简称 fia…

中科大计网学习记录笔记(一):Internet | 网络边缘

计算机网络 前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面…

时间序列预测 —— LSTM模型

时间序列预测 —— LSTM模型 1. 引言 时间序列预测是指在给定的历史时间序列数据上,使用模型来预测未来的数值。长短时记忆网络(Long Short-Term Memory, LSTM)是一种深度学习模型,广泛应用于时间序列预测任务。本文将介绍LSTM模型的理论基础、相关公式,分析其优缺点,并…

牛客,OR36 链表的回文结构,快慢指针和反转链表的实践

链表的回文结构_牛客题霸_牛客网 (nowcoder.com) 还是比较简单的&#xff0c;主要分为三个步骤&#xff0c;两种需掌握的函数实现 目录 主要思路过程&#xff0c;1&#xff0c;找到中间结点&#xff0c;2&#xff0c;反转中间结点往后的结点&#xff0c;3&#xff0c;遍历比…

ChatGPT 被曝泄露私密对话;美国 AI 企业一天蒸发 1.3 万亿市值丨 RTE 开发者日报 Vol.139

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

【产业实践】使用YOLO V5 训练自有数据集,并且在C# Winform上通过onnx模块进行预测全流程打通

使用YOLO V5 训练自有数据集,并且在C# Winform上通过onnx模块进行预测全流程打通 效果图 背景介绍 当谈到目标检测算法时,YOLO(You Only Look Once)系列算法是一个备受关注的领域。YOLO通过将目标检测任务转化为一个回归问题,实现了快速且准确的目标检测。以下是YOLO的基…

PVE报错处理:kvm [2205]: vcpu0 ignored RDMSR: 0x1b8

PVE使用过程中如果遇到&#xff1a;kvm [2205]: vcpu0 ignored RDMSR: 0x1b8 报错信息处理方法 vim /etc/modprobe.d/kvm.conf "options kvm ignore_msrsY"&#xff0c;这里在msrsY后面加一个空格&#xff0c;然后粘贴report_ignored_msrsN&#xff0c;使其变成 op…