Cpp学习——list的模拟实现

news2024/9/24 1:24:20

 

目录

一,实现list所需要包含的三个类

二,三个类的实现

1.list_node

2.list类

 3.iterator_list类

三,功能实现

1.list类里的push_back()

  2.iterator类里的运算符重载

3,list类里面的功能函数

1.insert()

2.erase()函数

3.clear()与析构函数

4.拷贝构造函数赋值运算符重载

5.打印函数


一,实现list所需要包含的三个类

     因为list是一个带头双向循环链表。所以list的实现会比较的复杂一些。总得来说,实现一个双向带头循环链表需要构造三个类。1.list类,2.list_node类,3.list_iterator类。前两个类相信大家会比较的熟悉。第三个类大家会比较不熟悉,因为它是一个迭代器的类。也就是说这个类是迭代器的封装。它的实现很有价值,因为它能让我们在使用list时也可以像之前的数据结构一样方便。

二,三个类的实现

1.list_node

 这首先要实现的便是节点类。这个类是最容易实现的。这个类的作用便是给要产生的节点画一个图,定义一下这个节点的结构和类型。代码如下:

template<class T>//模板
  	struct list_node
	{
		list_node(const T& val =T()//匿名对象 )//构造函数起初始化的作用
			:val(val)
			, prev(nullptr)
			, next(nullptr)
		{}

		T val;
		list_node<T>* prev;
		list_node<T>* next;
	};

现在你看到了,这个节点的结构便是这样。其实这与之前实现的那个带头双向循环链表的结构差不多。不过,在cpp中引入了模板的概念,所以这个节点便可以调用模板参数来进行泛化。

2.list类

list类的定义可太简单了。list的成员变量只有一个参数,便是_head。这是哨兵位的头节点。当然还有一个无参的构造函数和一个功能函数。代码如下:

template<class T>
	class list
	{ public:
		typedef list_node<T> Node;
		void empty()//初始化功能函数。
		{
			_head = new Node;
			_head->prev = _head;
			_head->next = _head;
		}
		list()//构造函数
		{
			empty();
		}

	private:
		Node* _head;

	};

当然,这里的list类也是要用模板来进行泛化的。

 3.iterator_list类

这个类就算是比较复杂的一个类了。因为迭代器要实现两种重载版本:1.普通版本。2.const版本。所以迭代器类的模板参数会有三个:

template <class T, class Ref, class ptr>

这三个模板参数会重载两种版本的三个参数:T对象,T&,T指针类型。在这个类里面也只有一个成员:_node。类型与list类里面的成员类型相同。该类代码如下:

template <class T, class Ref, class ptr>
	struct iterator_list
	{
		typedef list_node<T> Node;
		iterator_list(Node* node)
			:_node(node)
		{}

		Node* _node;

	};

三,功能实现

1.list类里的push_back()

首先来实现一个push_back()函数,这个函数的作用便是实现尾插数据。逻辑十分简单,代码如下:

void push_back(const T& val)
		{
			Node* newnode = new Node(val);
			Node* tail = _head->prev;

			tail->next = newnode;
			newnode->prev = tail;
			newnode->next = _head;
			_head->prev = newnode;
		}
		

写完这个以后,我便想要通过显示list里的数据来验证答案,但是很显然,我们做不到。因为list是一个自定义的类。但是我们有办法,所以我们便可以通过iterator来达到这个目的。所以我们必须在iterator类里面实现几个运算符重载。

  2.iterator类里的运算符重载

首先先实先这三个运算符重载:1.*,2.++,3.!=

代码如下:

        Ref operator *()//Ref代表T&
		{
			return _node->val;
		}

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

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

然后再在list类里面实现begin()与end()函数之后便可以实现范围for的使用了,end与begin代码如下:

       //实现两个版本的begin与end
        iterator begin()
		{
			return _head->next;
		}

		iterator end()
		{
			return _head;
		}

		const_iterator begin()const
		{
			return _head->next;
		}

		const_iterator end()const
		{
			return _head;
		}

在实现了上面的代码以后,为了让迭代器的功能更加全面,那我们再实现几个函数重载。再iterator_list里面的全部运算符重载如下:

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

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

		self operator++(int)//后置++
		{
			Node* tmp(*this);
			_node = _node->next;
			return tmp;
		}

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

		self operator--(int)//后置--
		{
			Node* tmp(*this);
			_node = _node->prev;
			return tmp;
		}

		bool operator!=(const self& it)//若用引用便要加上const
		{
			return _node != it._node;
		}

		bool operator==(self& it)
		{
			return _node == it._node;
		}

		self& operator+(int n)
		{
			while (n)
			{
				_node = _node->next;
				n--;
			}
			return *this;
		}

		ptr operator->()//箭头解引用
		{
			return &_node->val;
		}

在实现了这些运算符重载以后,list类里面的功能函数便好写了许多。

3,list类里面的功能函数

1.insert()

这个函数实现的功能便是在pos位置之前插入一个新的节点。这个pos的类型是迭代器类型。在list类里边的迭代器重定义为:

typedef iterator_list<T, T& ,T*> iterator;
typedef iterator_list<T, const T&, const T*> const_iterator;

我们只需要关注第一个迭代器即可,因为第二个迭代器修饰的对象是不可以修改的。所以insert的实现代码如下:

	iterator insert(iterator pos, const T& x)//在pos位置之前插入新节点
		{
			Node* newnode = new Node(x);
			Node* prev = pos._node->prev;

			prev->next = newnode;
			newnode->prev = prev;
			newnode->next = pos._node;
			pos._node->prev = newnode;

			return pos._node->prev;//防止迭代器失效,所以返回pos的前一个位置
			
		}

检验一下,检验代码如下:

void test_list2()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(5);

		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;

		l1.insert(l1.begin(), 9);
		l1.insert(l1.end(), 99);

		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;

	}

结果:正确

 在实现了insert()函数以后便可以复用实现push_back()与push_front()。代码如下:

        void push_back(const T& val)
		{
			insert(begin(), val);
		}

		void push_front(const T& val)
		{
			insert(end(), val);
		}

2.erase()函数

erase()函数实现的功能便是传入一个位置,然后将该位置上的节点删除掉。代码实现如下:

       iterator erase(iterator pos)
		{
			Node* prev = pos._node->prev;
			Node* next = pos._node->next;
			Node* cur = pos._node;

			delete cur;

			prev->next = next;
			next->prev = prev;

			return iterator(next);//返回新的迭代器

		}

复用erase实现尾删与头删,代码如下:

​
        void pop_back()
		{
			erase(--end());//尾巴是--end()的位置
		}

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

​

实验代码:

void test_list3()
	{
		list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(5);

		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;

		l1.insert(l1.begin(), 9);
		l1.insert(l1.end(), 99);

		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;


		l1.pop_back();
		l1.pop_front();
	   

		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;


	}

结果:正确

3.clear()与析构函数

实现了erase()函数以后再接再厉,接着复用实现clear函数,代码如下:

        void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//erase()每次都会返回下一个位置
			
			}
		}

从上面代码的逻辑便可以看出clear()函数是不会删除掉头节点的。但是析构函数需要。于是复用clear()函数后析构函数代码如下:

       ~list()
		{
			clear();
			delete _head;
		}

4.拷贝构造函数赋值运算符重载

拷贝构造函数实现过程大概分三步,首先new出来一个空间。然后再复用push_back()函数将要赋值的数据拷贝到新节点内。实现代码如下:

	list(const list<T>& l1)
		{
			empty();

			for (auto e : l1)
			{
				push_back(e);
			}
		}

实现了拷贝构造以后,按照惯例,赋值也要被安排上了。赋值运算符重载实现代码如下:

     list<T>& operator =( list<T>& l1)
		{
			if (this!= &l1)
			{
				clear();
				for (auto e : l1)
				{
					push_back(e);
				}
			}
			return *this;
		}

这是一个传统写法的运算符重载。如果想要更加精简可以写成现代写法,代码如下:

        void swap( list<T>& l1)//别加const
		{
			std::swap(_head, l1._head);//记得这个swap是std里面的swap
		}

		list<T>& operator=(list<T> l1)
		{
			if (this != &l1)
			{
				swap(l1);
			}

			return *this;
		}

5.打印函数

在这里,我们每次打印一个list对象时会很麻烦,每次都要用范围for来实现打印的功能。为了偷懒,我就想实现一个打印函数print来实现打印功能。实现代码如下:

template<class T>
	void print(list<T>& l1)
	{
		typename list<T>::iterator it = l1.begin();//这里要用typename为前缀来告诉编译器等list<T>实例化以后再来执行这条语句
		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;

	}

但是上面的代码针对的类型时list类的泛型,如果想要实现能加载更多容器的print()函数那就得改一下类型,改为如下代码:

template <class Contains>
	void print(Contains& l1)
	{
		typename Contains::iterator it = l1.begin();
		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

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

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

相关文章

vue技术学习

vue快速入门 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>vue快速入门</title> </head> <body> <!--老师解读 1. div元素不是必须的&#xff0c;也可以是其它元素&#xff0…

【C++】做一个飞机空战小游戏(十)——子弹击落炮弹、炮弹与飞机相撞

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

回归预测 | MATLAB实现SA-SVM模拟退火算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现SA-SVM模拟退火算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现SA-SVM模拟退火算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基本…

【华为鸿蒙3.0/荣耀安卓12使用VMOS Pro的激活方式】

关于华为鸿蒙3.0/荣耀安卓12如何解除进程限制&#xff0c;这里提供以下教程供小伙伴们参考。 需要准备&#xff1a;电脑一台&#xff0c;数据线一根。 第一步&#xff1a;打开手机USB调试模式。 确保USB数据线已连接手机和电脑&#xff0c;打开手机“开发者选项”&#xff0…

Monitor.Analog产品老化试验软件概要设计

Monitor.Analog产品老化试验软件概要设计&#xff1a; 1. 引言&#xff1a; 模拟量采集软件的目标是实现对模拟量信号的采集、处理和展示。该软件旨在提供一个用户友好的界面&#xff0c;允许用户配置采集参数、实时监测模拟量信号&#xff0c;并提供数据分析和导出功能。 2. 功…

K210学习笔记——三角函数下的目标追踪

各位正在读文章的朋友们你们好&#xff0c;本人为非专业学生&#xff0c;如有不对的地方&#xff0c;期待您的指正。 目标追踪的意思是&#xff1a;识别到目标物体&#xff0c;通过舵机转动&#xff0c;朝向目标物体. 实验器材&#xff1a;二自由度舵机云台加两个SG90舵机&…

预警:传统的QA岗位将被DevOps淘汰

导读在大多数机构或公司里&#xff0c;软件开发过程主要遵循一个或多个开发模型&#xff0c;例如瀑布模型或敏捷模型。在瀑布模型中&#xff0c;测试活动一般都在后期进行。软件开发完成后&#xff0c;缺陷被QA团队找出&#xff0c;然后再被修复。后两个活动不断循环和重复&…

Python基础语法入门(第二十三天)——正则表达式

正则表达式是一种文本模式&#xff0c;用于匹配字符串&#xff0c;它是由字符和特殊字符组成的模式。正则表达式可以用于验证、搜索、替换和提取字符串。其能够应用于各种编程语言和文本处理工具中&#xff0c;如Python、Java、JavaScript等。 正则表达式在线测试工具&#xf…

Spring源码编译-for mac

超详细的spring源码编译 记&#xff1a;编译成功时间&#xff1a;2023.08.19 环境准备&#xff1a; 1.idea 2023.1.1 Community Edition 2.jdk1.8 3.gradlegradle-5.6.4 4.spring源码(版本&#xff1a;spring-framework-v5.2.25.RELEASE) 一.spring源码下载 github 加速网站&…

stm32单片机4个开关控制8个LED灯,4种不同模式参考源代码(附带PROTEUS电路图)

//main.c文件 /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief : Main program body************************************************************************…

0101前期准备-大数据学习

文章目录 1 前言2 配置VMware虚拟机2.1 设置主机名和固定IP2.2 本地系统与Linux系统配置主机名映射2.3 配置虚拟机之间用户的SSH免密互通2.4 安装JDK环境2.5 关闭防火墙和SELinux2.6 更新时区和同步时间2.7 保存虚拟机快照 结语 1 前言 我们从基础的hadoop开始学起&#xff0c;…

Python上楼梯问题:递归解法探究(斐波那契变种)(记忆化递归)

文章目录 上楼梯问题&#xff1a;递归解法探究问题定义解决方案1. 递归2. 记忆化递归关于python memo{}默认参数和字典的语法语法功能版本信息注意事项 结论 上楼梯问题&#xff1a;递归解法探究 在这篇文章中&#xff0c;将对上楼梯问题进行深入探讨。上楼梯问题是一种常见的…

2D应用开发是选择WebGL 还是选择Canvas?

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 在介绍WebGL和Canvas的区别和联系之前&#xff0c;需要先了解它们各自的定义和特点。 WebGL是一种基于标准HTML5的技术&#xff0c;用于在Web浏览器中实时渲染3D图形。它是由Khronos Group开发的一套…

C++系列-内存模型

内存模型 内存模型四个区代码区全局区栈区堆区内存开辟和释放在堆区开辟数组 内存模型四个区 不同区域存放的数据生命周期是不同的&#xff0c;更为灵活。 代码区&#xff1a;存放函数体的二进制代码&#xff0c;操作系统管理。全局区&#xff1a;存放全局变量&#xff0c;常…

工厂模式的三种姿态?

在软件工程中常见的设计模式——工厂模式。工厂模式是一种有力的工具&#xff0c;用于创建对象实例的方式&#xff0c;可以帮助我们更好地组织代码和降低耦合性。在本文中&#xff0c;我将为大家详细介绍工厂模式的三种姿态&#xff0c;同时通过举例和代码演示来帮助大家更好地…

Docker环境安装elasticsearch和kibana

一、安装elasticsearch 创建es-network&#xff0c;让es、kibana在同一个网段&#xff1a; docker network create --driverbridge --subnet192.168.1.10/24 es-network运行elasticsearch docker run -d \ --name elasticsearch \ # 容器名 --hostname elasticsearch # 主机…

【Redis】Redis中的布隆过滤器

【Redis】Redis中的布隆过滤器 前言 在实际开发中&#xff0c;会遇到很多要判断一个元素是否在某个集合中的业务场景&#xff0c;类似于垃圾邮件的识别&#xff0c;恶意IP地址的访问&#xff0c;缓存穿透等情况。类似于缓存穿透这种情况&#xff0c;有许多的解决方法&#xf…

高效的WMS系统手持盘点方案

WMS系统手持盘点就是指利用WMS系统支持的手持式电子盘点设备进行库存盘点的方式。 具体来说: - 手持盘点设备是一种小型的电子设备,具有移动条形码扫描功能,可以实时与WMS系统联通。 - WMS系统利用手持设备,可以给仓储人员下发具体的盘点任务,例如需要盘点的货位、商品等信息…

mybatis plus 配置自动设置创建时间和创建人id

1.新建 MyMetaObjectHandler package com.ruoyi.framework.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.ruoyi.common.bean.LocalUser; import com.ruoyi.coupon.domain.CouponUser; import org.apache.ibatis.reflection.MetaObjec…

软件报错msvcr90.dll丢失的解决方法,亲测可以修复

我曾经遇到过一个令人头疼的问题&#xff1a;msvcr90.dll丢失。这个问题导致了我的程序无法正常运行&#xff0c;让我感到非常苦恼。然而&#xff0c;在经过一番努力后&#xff0c;我终于成功地修复了这个问题&#xff0c;这让我感到非常欣慰和满足。 msvcr90.dll丢失的原因可能…