链表的实现(C++版)

news2024/9/23 11:23:12

对于链表的学习,之前在C语言部分的时候就已经有学习过,也学会了使用C语言来打造一个链表.如今学了C++ 则想通过C++来打造一个链表,以达到锻炼自己的目的.

1.链表的初步实现

1.节点模板的设置

	template <class T> 
	struct ListNode
	{
		ListNode <T>* _next;
		ListNode <T>* _prve;
		T data;

		ListNode(const T& x = T())
			:_next(nullptr)
			,_prve(nullptr)
			,_data(x)
		{}
	};

这边使用一个模板来定义这个节点,因为我们传进来的数据可能是任意类型的,为了方便数据的传参,我们直接使用template <class T>来写一个万能的模板.除了next指针和prve指针和data以外,我们还得写一个构造函数,这边的构造函数的初始化就是把_next金额_prve初始化成一个空指针,然后_data来根据传入的参数来做决定. 传入的参数为 const T&x = T() 这里用的是传引用传参,然后如果有x的话,就用x来初始化,如果没有x的话,后面带着的这个T()就可以对其调用默认的构造函数了. 如果不给T()的话,就得自己在传递一个参数.

2.链表模板的设置

	template <class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prve = _head;
			_head->data = 0;
		}

		void push_back(const T& x)
		{
			Node* newnode = new Node(x);
			Node* Tail = _head->_prve;
			Tail->_next = newnode;
			newnode->_prve = Tail;
			newnode->_next = _head; 
			_head->_prve = newnode;

		}

	private:
		Node* _head;

	};

这里也没什么好说的,生成了一个带头链表,尾插就是一个链表的基本操作.

此时我们想打印出来看看,该怎么做?

想到用迭代器来进行打印,但是,由于我们是自己实现链表,因此又重新开了个命名空间,因此,iterator, !=, *, ++等符号我们都是用不了的,这些我们都得自己来进行运算符重载.因此,实现C++ 的链表,难的不是链表本身,而是实现链表的迭代器,这是一个麻烦的过程.

3.迭代器的模拟

对于迭代器,他有一个好处,就是不管你的数据底层是一个什么样子的数据,他都可以对你进行访问.这可能让我们会联想到指针,对于指针来说,数据的原生指针就是一个天然的迭代器,但是,这是建立在一个你的数据的物理空间是连续的情况下的基础的,链表都是随机生成的节点地址,他在物理空间上是不连续的.

因此我们想要模拟一个迭代器的行为,首先定义一个迭代器的类

我们 可以看到, ListIerator这个迭代器,就是一个单参数的构造函数.

1.重载运算符++

我们想要的是这个迭代器++之后会指向下一个节点,而由于在链表中,直接++是无法让当前的迭代器指向下一个节点的,因此我们需要对其进行重载一下.

		//前置++
		iterator& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		
		//后置++
		iterator operator++(int)
		{
			iterator tmp(*this);
			_node = _node->_next;
			return tmp;
		}

在这里,我们把ListIterator类重定义成了iterator,在重载运算符中,由于我们返回的数据是有类型的,而这个*this指针的类型则是当前的ListIterator也就是iterator,因此,在这里我们要把重载函数的类型写成iterator.

而在重载后置++的时候,由于我们需要的是返回++之前的值,因此,我们需要另一个迭代器来获取返回之前的数据,得完成一个拷贝构造,因此这边重载后置++的时候就需要浅拷贝,因而不用加&.

2.重载运算符--

		//前置--
		iterator& operator--()
		{
			_node = _node->_prve;
			return *this;
		}

		//后置--
		iterator operator--(int)
		{
			iterator tmp(*this);
			_node = _node->_prve;
			return tmp;
		}

这个跟重载++是类似的.

3.begin()函数和end()函数

		iterator begin()
		{
			//iterator it(_head->next);
			//return it;
			//return _head->next; (这个便是隐式类型转换)
			return iterator(_head->_next);

		}
		iterator end()
		{
			//iterator it(_head);
			//return it;
			//return _head; (这个也是隐式类型转换)
			return iterator(_head);
		}

这边的begin函数和end函数都是为了获取当前位置的迭代器,因此就使用了迭代器类型来进行返回当前的节点.

最后,我们还需要重载一下!=号和==号

		bool operator!=(const iterator& it)
		{
			return _node != it._node;
		}
		bool operator==(const iterator& it)
		{
			return _node == it._node;
		}

目前,当前的这个链表,就可以进行使用啦~

接下来就是对这个链表进行完善了.

4.insert函数的实现

		void insert(iterator pos, const T& val)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(val);
			//prve newnode cur next
			Node* prve = cur->_prve;
			prve->_next = newnode;
			newnode->_prve = prve;
			newnode->_next = cur;
			cur->_prve = newnode;
		}

这些已经没什么好说的了,跟之前用C语言实现的链表的基本逻辑是一样的.

而当我们实现了insert函数的时候,就可以让他在push_back和push_front来复用.

		void push_back(const T& x)
		{
			/*Node* newnode = new Node(x);
			Node* Tail = _head->_prve;
			Tail->_next = newnode;
			newnode->_prve = Tail;
			newnode->_next = _head;
			_head->_prve = newnode;*/

			insert(end(), x);

		}

		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_back()
		{
			erase(--end());//删除最后一个节点
		}

		void pop_fornt()
		{
			erase(begin());
		}

5.erase函数的实现

		void erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prve = cur->_prve;
			Node* next = cur->_next;
			//prve cur next
			prve->_next = next;
			next->_prve = prve;
			delete cur;
		}

这里需要注意的一个地方就是当我们释放了cur节点的时候,cur所指向的pos就也会被释放掉了,然后当前的pos就造成一个迭代器失效的问题,因此,我们需要返回pos节点的下一个节点.

		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prve = cur->_prve;
			Node* next = cur->_next;
			//prve cur next
			prve->_next = next;
			next->_prve = prve;
			delete cur;
			return iterator(next);
		}

2.链表继续优化

1.重载->

以上的仅仅只是支持用来插入int,float,double等数据.

而当我们遇到一个结构体类型的数据,如果继续使用以上的方法是会报错的.


 

如果想直接来读取数据,只能是用这种方法.

这里就是it来调用operator*,然后返回的就是 A 由于 A里面不止是有一个数据,因此还需要进行一次解引用,来读取_a1 或者 _a2

但是这种方法相对来说是非常不方便的.

因此,我们则可以对->来进行重载.

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

这个写法乍一看是比较诡异的,但自己推敲之后,发现还是比较好理解的,

首先,我们上面的那种写法,是先把迭代器it解引用一遍,然后再通过 . 来进行解引用第二遍来获取里面的数据,而这里重载的->,则是直接返回的就是 A*(T*) , 然后 A*就会自己来解引用,进而就会得到里面的数据.

修改之后的遍历是这样的,

而其中的又是编译器简化了,我们可以拆开看看

这两种写法是一样的,上面的那种就是it先调用->重载,然后再解引用,而下面这种是直接省略了,编译器帮我们做了,是为更加简便的方法.

2.模板的传引用和传指针

目前我们的模板仅仅是能返回不是const的数据,当我们有需求来返回const的数据时,我们能想到的是把这个类再复制一边,然后加个const来修饰,但是这样是过于麻烦的.

当除了类型,其它的内容都一样的时候,我们就可以考虑使用模板来更好的传递数据.

	template <class T , class Ref , class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T> iterator;
		Node* _node;
		ListIterator(Node* node)
			:_node(node)
		{}

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

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

		//前置++
		iterator& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		//后置++
		iterator operator++(int)
		{
			iterator tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		//前置--
		iterator& operator--()
		{
			_node = _node->_prve;
			return *this;
		}

		//后置--
		iterator operator--(int)
		{
			iterator tmp(*this);
			_node = _node->_prve;
			return tmp;
		}

		bool operator!=(const iterator& it)
		{
			return _node != it._node;
		}
		bool operator==(const iterator& it)
		{
			return _node == it._node;
		}



	};

可以看到,我在声明模板的时候,新加入了两个类名,一个是Ref , 一个是Ptr,代表着传引用和传指针,然后写道对应的函数上.

而在list里面, 我们可以这样做

一个是传T , 然后是T& , 然后是 T* ,这样就可以使得可以根据所需来返回我们需要的数据了.

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

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

相关文章

【Linux 网络】应用层

文章目录 应用层协议序列化的概念jsoncpp 1. HTTP1.1URLURL编解码 1.2 HTTP的格式HTTP请求格式HTTP响应格式 1.3 HTTP的方法GET/POST 1.4 HTTP的状态码1.5 HTTP的报头cookie和session1.6 简单HTTP服务器 2. HTTPS2.1 加密方式对称加密非对称加密全对称加密混合加密密钥协商对称…

CSS画箭头

向右示例 .arrows {height: 7px;width: 7px;background-color: transparent;border-top: 2px solid rgba(0, 0, 0, 0.3);border-right: 2px solid rgba(0, 0, 0, 0.3);transform: rotate(45deg);margin-left: 6px; } 可以尝试将其封装为组件&#xff08;以微信小程序为例&…

上位机《1》 步进电机、步进驱动器,连接端子(接线盒子)等

正运动技术 固高控制卡 雷赛控制卡 步进电机 电机内部的线圈数不同&#xff0c;组成的电机相数也不同&#xff0c;两相步进电机电机内部是由2个线圈组成&#xff0c;而三相步进电机内部是由3个线圈。 相数越多&#xff0c;步进角越小。所有精度就越高。步进电机转速越快&…

经验分享:大数据多头借贷风险对自身的不利影响?

在现代金融体系中&#xff0c;大数据技术的应用使得多头借贷成为一种普遍现象。多头借贷指的是个人或企业在短时间内同时或近期内申请多笔贷款或信用产品&#xff0c;这种行为可能带来一系列财务和信用风险。以下是大数据多头借贷风险对个人自身可能产生的不利影响&#xff1a;…

花了2小时,自己做了一个出入库系统

出入库管理是库存管理工作的重中之重&#xff0c;但在刚做产品出入库时&#xff0c;我可是踩了不少坑—— 库存管理不精确仓库作业效率低下货物追踪困难报表统计繁琐... 后来我就自学了下&#xff0c;花了两个小时自己做了一套织信出入库管理系统&#xff0c;全程没有敲一个代…

帆软10.0报表部署到正式环境后,不显示数据集的数据。

修改正式环境的数据连接 1&#xff09;数据连接的「编码」类型设置为「默认」。 2&#xff09;数据连接的「数据连接URL」后加后缀&#xff0c;如下图所示。格式为&#xff1a; jdbc:mysql://hostname:port/database?generateSimpleParameterMetadatatrue&useUnicodetru…

运维工程师,刚入职一个之前没有运维的公司,该做什么?

运维工程师&#xff0c;刚入职一个之前没有运维的公司&#xff0c;该做什么&#xff1f; 下面内容&#xff0c;只谈技术工作方面的&#xff0c;人情世故的方面自己体会吧。。。。 前言 写这个&#xff0c;主要是有一个朋友&#xff0c;也是运维工程师&#xff0c;五年以上运…

【STL】之 list 使用方法和模拟实现

目录 前言&#xff1a; list是什么&#xff1f; 节点类 迭代器类&#xff1a; list类 list的迭代器失效问题 前言&#xff1a; 之前我们分别手撕了string类和vector类&#xff0c;今天我们来跟list类打打交道~ list是什么&#xff1f; 通过查c文档可知&#xff0c;list…

(2)基于巴法云+MQTT+微信小程序控制esp8266点灯

目录 1、wifi配置指令表 2、连接连接wifi网络 3、连接巴法云MQTT &#xff08;1&#xff09;配置用户属性ATMQTTUSERCFG ① 命令格式&#xff1a; ② 命令参数&#xff1a; ③ 实际配置方式&#xff1a; &#xff08;2&#xff09;配置ESP 设备连接的 MQTT broker ① 命令格式…

【AI大模型】自动辅助驾驶的“大模型”时代

&#x1f388;边走、边悟&#x1f388;迟早会好 一、自动辅助驾驶实现与设计 1. 系统架构 1.1. 传感器系统 摄像头&#xff1a;提供前视、侧视、后视等多角度图像数据&#xff0c;用于检测车道线、交通标志、行人和其他车辆。雷达&#xff08;RADAR&#xff09;&#xff1a…

【python】PyQt5中QRadioButton的详细用法教程与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

论文速递 | Operations Research 6月文章合集

编者按&#xff1a; 在本系列文章中&#xff0c;我们梳理了运筹学顶刊Operations Research在2024年6月份发布的9篇相关文章的基本信息&#xff0c;旨在帮助读者快速洞察领域新动态。 推荐文章1 题目&#xff1a;Tight Guarantees for Multiunit Prophet Inequalities and On…

list模拟实现--用节点封装的迭代器问题

目录 一、list的使用 1.1list的构造 1.2list的iterator ​编辑 1.3 list的capacity 1.4 list的element access ​编辑 1.5list的mdifiers ​编辑 二、list的迭代器失效问题 三、list的模拟实现 3.1定义一个节点类 3.2用节点去封装迭代器 编译器对->的优化问题 …

JavaScript做网页是否过期的处理

通过路由上的参数生成唯一md5和路由上token做验证_md5 token-CSDN博客 前言&#xff1a;基于这篇文章我们做网页是否超时&#xff0c;网页是否过期的处理。打开一个网页允许他在一定时间内可以访问&#xff0c;过了这个时间就不可以访问了&#xff0c;encrypt是h5加密方法&…

数据仓库基础理论—维度建模(图文详解)

数据仓库基础理论—维度建模 维度建模是数据仓库设计中的一种核心方法&#xff0c;旨在以业务角度组织数据&#xff0c;使其更易于理解、查询和分析。 1. 维度模型的基本概念 1.1 事实表&#xff08;Fact Table&#xff09;&#xff1a; 事实表是维度模型的核心&#xff0…

ARM编程指令二

一、算术指令 1. add指令 功能: 将两个操作数相加&#xff0c;并将结果存储在目标寄存器中。 ADD R0, R1, R2 // R0 R1 R22. sub指令 功能: 将第二个操作数从第一个操作数中减去&#xff0c;并将结果存储在目标寄存器中。 SUB R0, R1, R2 // R0 R1 - R23.ADC指令- 带…

强化学习机械臂

一.前言 这里记录一下我学习强化学习的一些知识&#xff0c;并希望在今后可以通过仿真成功验证算法&#xff0c;如果时间允许的情况下希望可以结合到真实机械臂上。 二.学习过程 机械臂强化学习实战&#xff08;stable baselines3panda-gym&#xff09; 这里我先用anaconda创…

【原创教程】电气电工常用剥线钳和压线钳(入门篇)

今天我们来看一下电气电工经常会用到的工具&#xff0c;剥线钳和压线钳。 首先我们看剥线钳做什么用&#xff1f;主要就是剥线&#xff0c;让内部的铜丝裸露。我们来看一下&#xff0c;我们经常用到的剥线钳。 1、带刃口剥线钳 2、自动剥线钳 3、鸭嘴剥线钳 下面看压嘴剥线钳…

Nodejs的使用

1.安装nodejs服务器。 java 项目可以运行在 tomcat 服务器&#xff0c;开始完成前后端完全分离。前端有自己独立的工程。我们需 要把前端独立的工程运行起来。 --- 运行在 nodejs 服务器下。 理解为 tomcat 服务器 安装成功后在命令窗口查看 1.1 安装npm java 项目需要依赖…

finalshell连接kali-Linux失败问题略谈

如果你正在使用fianlshell或者xshell等终端软件远程连接Linux进行工作&#xff0c;但是突然有一天&#xff0c;你死活连不上了&#xff0c;报错提示如下&#xff1a; java.net.ConnectException: Connection refused: connect 就像这样&#xff1a; 哪怕是重装虚拟机&#xff0…