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都存在很多相似之处,所以建议将这三者放在一起学习。

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

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

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

相关文章

Stable Diffusion扩散模型推导公式的基础知识

文章目录 1、独立事件的条件概率2、贝叶斯公式、先验概率、后验概率、似然、证据3、马尔可夫链4、正态分布 / 高斯分布5、重参数化技巧6、期望7、KL散度 、高斯分布的KL散度8、极大似然估计9、ELBO :Evidence Lower Bound10、一元二次方程 1、独立事件的条件概率 A 和 B 是两个…

Rredis缓存常见面试题

文章目录 1.什么是缓存穿透&#xff0c;怎么解决2.什么是缓存击穿&#xff0c;怎么解决3.什么是缓存雪崩&#xff0c;怎么解决4.双写一致性问题5.redisson添加的排他锁是如何保证读写、读读互斥的6.为什么不使用延迟双删7.redis做为缓存&#xff0c;数据的持久化是怎么做的8.re…

【热门话题】文言一心与ChatGPT-4:一场跨时代智能对话系统的深度比较

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 文言一心与ChatGPT-4&#xff1a;一场跨时代智能对话系统的深度比较一、技术背景…

成绩管理系统|基于springboot成绩管理系统的设计与实现(附项目源码+论文)

基于springboot成绩管理系统的设计与实现 一、摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装毕业设计成绩管…

HarmonyOS应用开发ArkUI(TS)电商项目实战

项目介绍 本项目基于 HarmonyOS 的ArkUI框架TS扩展的声明式开发范式&#xff0c;关于语法和概念直接看官网官方文档地址&#xff1a;基于TS扩展的声明式开发范式&#xff0c; 工具版本&#xff1a; DevEco Studio 3.1 Canary1 SDK版本&#xff1a; 3.1.9.7&#xff08;API V…

海外媒体软文发稿:带动海外宣发新潮流,迈向国际舞台

引言 随着全球化的发展&#xff0c;越来越多的中国企业希望在国际舞台上展示自己的实力。而海外媒体软文发稿作为一种全新的海外宣传方式&#xff0c;正逐渐成为带动海外宣发新潮流的有力工具。本文将探讨海外媒体软文发稿的优势和如何迈向国际舞台。 海外媒体软文发稿的优势…

代码随想录阅读笔记-二叉树【最大二叉树】

题目 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构建最大二叉树&#x…

linux系统负载对系统的意义

负载平均值的含义 负载平均值是通过uptime和top命令显示的三个数字&#xff0c;分别代表不同时间段的平均负载&#xff08;1分钟、5分钟和15分钟的平均值&#xff09;。这三个数字越低越好&#xff0c;较高的数字意味着系统可能存在问题或过载。然而&#xff0c;并没有一个固定…

男生穿什么裤子最帅?必备的男生裤子推荐

每个人都想拥有很多条好看质量又好的裤子。不过市面上有太多服装品牌&#xff0c;甚至还有不少劣质的衣裤&#xff0c;穿洗两遍之后就出现松垮、变形的情况。为了能够让大家可以选到合适的衣裤&#xff0c;我自费购买了多个品牌的裤子&#xff0c;并给出大家测评结果。 购买到质…

网站访问502,网站服务器崩溃,比较常见几个的原因

其实&#xff0c;配置再好的服务器也难免在使用过程中出现一些故障&#xff0c;造成宕机。 服务器一旦出现故障&#xff0c;影响到用户实时访问网站&#xff0c;造成用户流失&#xff0c;如果在企业的销售高峰期&#xff0c;则将直接影响到商业利润&#xff0c;而且不仅影响外…

SD-WAN降低网络运维难度的三大关键技术

企业网络作为现代企业不可或缺的基础设施&#xff0c;承担着连接全球的重要任务。随着全球化和数字化转型的加速推进&#xff0c;企业面临着越来越多的网络挑战和压力。传统的网络组网方式已经不能满足企业规模扩大、分支机构增多、上云服务等需求&#xff0c;导致了网络性能下…

消除歧义:利用动态上下文提出有效的RAG问题建议

原文地址&#xff1a;disambiguation-using-dynamic-context-in-crafting-effective-rag-question-suggestions 2024 年 4 月 3 日 这一策略唤起了IBM沃森率先采用的一项技术&#xff1a;消除歧义。面对用户模糊不清的输入&#xff0c;系统会提供大约五个或更少的选项供用户挑…

软件架构风格_3.以数据为中心的体系结构风格

以数据为中心的体系结构风格主要包括仓库体系结构风格和黑板体系结构风格。 1.仓库体系结构风格 仓库&#xff08;Repository&#xff09;是存储和维护数据的中心场所。在仓库风格&#xff08;见图1&#xff09;中&#xff0c;有两种不同的构件&#xff1a;中央数据结构说明当…

5米分辨率数字高程模型(DEM)的制作

在现代科技的驱动下&#xff0c;地理信息系统&#xff08;GIS&#xff09;和遥感技术已经取得了惊人的进展。其中一项令人瞩目的技术就是5米分辨率数字高程模型&#xff08;DEM&#xff09;的制作&#xff0c;它是基于多颗高分辨率卫星数据为原始数据&#xff0c;借助智能立体模…

Android 性能优化之黑科技开道(一)

1. 缘起 在开发电视版智家 App9.0 项目的时候&#xff0c;发现了一个性能问题。电视系统原本剩余的可用资源就少&#xff0c;而随着 9.0 功能的进一步增多&#xff0c;特别是门铃、门锁、多路视频同屏监控后等功能的增加&#xff0c;开始出现了卡顿情况。 经过调研分析发现有…

Apache DolphinScheduler 【安装部署】

前言 今天来学习一下 DolphinScheduler &#xff0c;这是一个任务调度工具&#xff0c;现在用的比较火爆。 1、安装部署 1.0、准备工作 1.0.1、集群规划 dolphinscheduler 比较吃内存&#xff0c;所以尽量给 master 节点多分配一点内存&#xff0c;桌面和虚拟机里能关的应用…

P2249 【深基13.例1】查找 (二分)

题目链接 代码&#xff1a; #include<algorithm> #include<iostream> #include<cstring> #include<queue> #include<cmath>using namespace std; //就是找左端点&#xff0c;没有输出-1 int n; int q; int a[1000010];int main() {scanf("…

Qt QML的枚举浅用

QML的枚举用法 序言概念命名规则在QML定义枚举的规范 用法QML的枚举定义方法供QML调用的&#xff0c;C的枚举定义方法 序言 概念 QML的枚举和C的其实差不多&#xff0c;但是呢&#xff0c;局限比较多&#xff0c;首先不能在main.qml里定义&#xff0c;也不能在子项中定义。 …

Java入门教程||Java 多线程编程

Java 多线程编程 Java 给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程&#xff0c;并且每个线程定义了一个独立的执行路径。 多线程是多任务的一种特别的形式。多线程比多任务需要更小的开销。 这里定义和线程…

晶核养号攻略,小白必读攻略!

晶核手游作为一款深受玩家喜爱的游戏&#xff0c;养号是玩家们在游戏中常常会碰到的问题之一。在这个攻略中&#xff0c;我们将为新手玩家们提供一些养号的建议和技巧&#xff0c;帮助他们更好地管理和提升自己的游戏账号。 1. 初始阶段的金币管理 在游戏初期&#xff0c;前60…