C++_list

news2024/11/16 1:40:23

目录

一、模拟实现list

1、list的基本结构

2、迭代器封装

2.1 正向迭代器

2.2 反向迭代器 

3、指定位置插入

4、指定位置删除

5、结语


前言:

        list是STL(标准模板库)中的八大容器之一,而STL属于C++标准库的一部分,因此在C++中可以直接使用list容器存储数据。list实质上是一个带头双向循环链表,这也使得他能够在常数的时间复杂度范围内插入和删除数据,缺点是不能像数组那样进行元素下标的随机访问。

一、模拟实现list

        在C++中可以直接调用库里的list,并且使用起来非常的简便,和使用vector、string相差无几,但是为了能够更好的了解list和其底层原理,下文会对lsit的常用接口进行模拟实现,以便对list有更深入的理解,并且list的底层实现逻辑完美的表现了面向对象的思想。

1、list的基本结构

        与实现vector和string不一样,list可以分成两个整体:链表本身、节点本身,因此需要两个类(链表类、节点类)完成list的基本实现,并且把节点类看作是一个普通的节点,而实现链表的具体功能函数都放在链表类中。

        list基本功能代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

namespace ZH
{
	template <class T>
	struct list_node//节点类
	{
		list_node<T>* prev;
		list_node<T>* next;
		T val;

		list_node(const T& t)
			:prev(nullptr)
			, next(nullptr)
			, val(t)
		{}
	};

	template <class T>
	class list//链表类
	{
	public:
		typedef list_node<T> node;//让节点类看起来像一个普通的节点

		list()//构造函数、目的是创建哨兵位头节点
			:_node(new node(-1))
		{
			_node->next = _node;
			_node->prev = _node;
		}

		void push_front(const T& t)//头插
		{
			node* newnode = new node(t);
			node* cur = _node->next;

			_node->next = newnode;
			newnode->prev = _node;
			newnode->next = cur;
			cur->prev = newnode;
		}

		void Print()//打印数据
		{
			node* cur = _node->next;
			while (cur != _node)
			{
				cout << cur->val << " ";
				cur = cur->next;
			}
		}

		~list()//析构
		{
			node* cur = _node->next;
			node* dest = cur->next;
			while (cur != _node)
			{
				delete cur;
				cur = dest;
				dest = dest->next;
			}
			delete _node;
			_node = nullptr;
		}

	private:
		node* _node;

	};
}

int main()
{
	ZH::list<int> lt;
	lt.push_front(1);
	lt.push_front(2);
	lt.push_front(3);
	lt.push_front(4);
	lt.Print();
	return 0;
}

        运行结果:

        从上面的代码可以发现, list的底层实现和之前c语言中实现双向循环链表的逻辑一模一样,只不过用C++的方式将其进行了封装。

        这里节点类里的成员变量要放在公有域(struct定义类默认为公有域),因为在list中会改变节点前后指针的指向,因此节点的指针要设为外部可见。

2、迭代器封装

2.1 正向迭代器

        在调用库里面的list,会发现库里面list的迭代器用起来像是一个指针,因为可以对迭代器进行解引用操作以及++操作。所以在模拟实现迭代器时,我们也用一个指针来模拟迭代器的行为,不同的是指针进行++操作时,会指向该地址的下一个地址,而我们期望的迭代器++可以指向下一个节点。

        示意图如下:

        因此,如果单单的把指针看成迭代器则无法实现遍历链表的功能,所以要实现迭代器必须对指针进行又一层的封装,也就是把迭代器写成一个类,但是该类的底层还是一个节点指针,只是对该指针的操作有了新的规定。

        正向迭代器代码实现如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

namespace ZH
{
	template <class T>
	struct list_node//节点类
	{
		list_node<T>* prev;
		list_node<T>* next;
		T val;

		list_node(const T& t)
			:prev(nullptr)
			, next(nullptr)
			, val(t)
		{}
	};

	template<class T>
	struct list_iterator//迭代器类
	{
		typedef list_node<T> node;
		typedef list_iterator<T> self;

		node* _node;// 成员变量

		list_iterator(node* node)
			:_node(node)
		{}

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

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

		T& operator*()//重载解引用
		{
			return _node->val;
		}

	};

	template <class T>
	class list//链表类
	{
	public:
		typedef list_node<T> node;//让节点类看起来像一个普通的节点
		typedef list_iterator<T> iterator;//让迭代器类看起来像一个迭代器

		list()//构造函数、目的是创建哨兵位头节点
			:_node(new node(-1))
		{
			_node->next = _node;
			_node->prev = _node;
		}

		void push_front(const T& t)//头插
		{
			node* newnode = new node(t);
			node* cur = _node->next;

			_node->next = newnode;
			newnode->prev = _node;
			newnode->next = cur;
			cur->prev = newnode;
		}

		iterator begin()
		{
			//begin返回的是一个指向头节点的下一个节点的迭代器对象
			return iterator(_node->next);//匿名对象创建并返回
		}

		iterator end()
		{
			//begin返回的是一个指向头节点的迭代器对象
			return iterator(_node);//匿名对象创建并返回
		}

		~list()//析构
		{
			node* cur = _node->next;
			node* dest = cur->next;
			while (cur != _node)
			{
				delete cur;
				cur = dest;
				dest = dest->next;
			}
			delete _node;
			_node = nullptr;
		}

	private:
		node* _node;//指向哨兵位的头节点

	};
}

int main()
{
	ZH::list<int> lt;
	lt.push_front(1);
	lt.push_front(2);
	lt.push_front(3);
	lt.push_front(4);
	ZH::list<int>::iterator it = lt.begin();
	while (it!=lt.end())
	{
		cout << *it << " ";
		++it;
	}
	return 0;
}

        运行结果:

        注意,这里的it要看成是一个对象,他的类型是 list<int>::iterator。ZH::list<int>::iterator it = lt.begin();这句代码实际上是调用了拷贝构造,用begin()的返回对象构造了一个新的对象it。

2.2 反向迭代器 

        反向迭代器与正向迭代器的区别在于,反向迭代器是从链表的最后一个节点开始往前遍历的,并且他的逻辑和正向迭代器的逻辑是相反的,可以确定的一点是反向迭代器也是通过一个类来实现的。

        反向迭代器实现代码如下:

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

namespace ZH
{
	template <class T>
	struct list_node//节点类
	{
		list_node<T>* prev;
		list_node<T>* next;
		T val;

		list_node(const T& t)
			:prev(nullptr)
			, next(nullptr)
			, val(t)
		{}
	};

	template<class T>
	struct list_iterator//迭代器类
	{
		typedef list_node<T> node;
		typedef list_iterator<T> self;

		node* _node;// 成员变量

		list_iterator(node* node)
			:_node(node)
		{}

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

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

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

		T& operator*()//重载解引用
		{
			return _node->val;
		}

	};

	template<class T>
	class list_reverse_iterator
	{
	public:
		typedef list_iterator<T> iterator;
		typedef list_reverse_iterator<T> rself;

		list_reverse_iterator(iterator it)
				:rit(it)
		{}

		bool operator!=(const rself& s)//重载!=
		{
			return rit!= s.rit;//复用正向迭代器的!=重载
		}

		rself& operator++()//重载前置++
		{
			--rit;//复用正向迭代器的++
			return *this;
		}

		T& operator*()//重载解引用
		{
			return *rit;//复用正向迭代器的解引用
		}

	private:
		iterator rit;
	};

	template <class T>
	class list//链表类
	{
	public:
		typedef list_node<T> node;//让节点类看起来像一个普通的节点
		typedef list_iterator<T> iterator;//让迭代器类看起来像一个迭代器
		typedef list_reverse_iterator<T> reverse_iterator;

		list()//构造函数、目的是创建哨兵位头节点
			:_node(new node(-1))
		{
			_node->next = _node;
			_node->prev = _node;
		}

		void push_front(const T& t)//头插
		{
			node* newnode = new node(t);
			node* cur = _node->next;

			_node->next = newnode;
			newnode->prev = _node;
			newnode->next = cur;
			cur->prev = newnode;
		}

		reverse_iterator rbegin()
		{
			return reverse_iterator(iterator(_node->prev));//返回最后一个节点
		}

		reverse_iterator rend()
		{
			return reverse_iterator(iterator(_node));//返回头节点
		}

		~list()//析构
		{
			node* cur = _node->next;
			node* dest = cur->next;
			while (cur != _node)
			{
				delete cur;
				cur = dest;
				dest = dest->next;
			}
			delete _node;
			_node = nullptr;
		}

	private:
		node* _node;//指向哨兵位的头节点

	};
}

int main()
{
	ZH::list<int> lt;
	lt.push_front(1);
	lt.push_front(2);
	lt.push_front(3);
	lt.push_front(4);
	ZH::list<int>::reverse_iterator rit = lt.rbegin();
	while (rit != lt.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	return 0;
}

        运行结果:

        从上述代码中可以发现,反向迭代器是复用正向迭代器的成员函数达到实现的,只不过在反向迭代器类里进行又一层包装。

3、指定位置插入

        有了迭代器,就可以实现从指定位置插入了,指定位置插入代码如下:

void Insert(iterator pos, const T& val)//插入
		{
			node* newnode = new node(val);
			node* cur = pos._node;
			node* prev = cur->prev;

			prev->next = newnode;
			newnode->prev = prev;
			newnode->next = cur;
			cur->prev = newnode;
		}

         值得注意的是,list的插入不会导致迭代器失效,因为即使在该迭代器指向节点的前面插入一个数据,则该迭代器还是指向该节点的。

4、指定位置删除

        指定位置删除代码如下:

void 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;
		}

        删除逻辑示意图:

        删除与插入不同在于,删除之后迭代器会失效,因为此时it指向的是一块被回收的区域,不能直接访问it所指向的区域,会导致野指针问题。 

5、结语

        以上就是关于list如何实现的讲解,list的模拟实现完全体现了面向对象的思想,链表本身、节点以及迭代器都被封装成一个类,从用户的角度看,是在对象的层面上直接进行操作的,但是底层却是各种复用,依旧是通过操作内置类型来实现上层的对象之间的操作。

        最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!

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

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

相关文章

实现扫码登录

扫码登录是如何实现的&#xff1f; 二维码信息里主要包括唯一的二维码ID,过期的时间&#xff0c;还有扫描状态&#xff1a;未扫描、已扫描、已失效。 扫码登录流程 用户打开网站登录页面的时候&#xff0c;浏览器会向二维码服务器发送一个获取登录二维码的请求。二维码服务器收…

雨云VPS搭建幻兽帕鲁服务器,PalWorld开服联机教程(Windows),0基础保姆级教程

雨云VPS用Windows系统搭建幻兽帕鲁私服&#xff0c;PalWorld开服联机教程&#xff0c;零基础保姆级教程&#xff0c;本教程使用一键脚本来搭建幻兽帕鲁服务端&#xff0c;并讲了如何配置游戏参数&#xff0c;如何更新服务端等。 最近这游戏挺火&#xff0c;很多人想跟朋友联机…

顶象点选验证码

要放假了好颓废。。。。 没啥事儿干&#xff0c;就把之前剩余的顶象点选系列的验证码类型看了下。 之前分享了一篇关于这个顶象的滑块的 DX算法还原_dx算法还原_逆向学习之旅-CSDN博客&#xff0c;感兴趣可以去看看。 咱们以文字点选为例&#xff1a; def get_image_arry(s…

Spring Boot如何统计一个Bean中方法的调用次数

目录 实现思路 前置条件 实现步骤 首先我们先自定义一个注解 接下来定义一个切面 需要统计方法上使用该注解 测试 实现思路 通过AOP即可实现&#xff0c;通过AOP对Bean进行代理&#xff0c;在每次执行方法前或者后进行几次计数统计。这个主要就是考虑好如何避免并发情况…

AI绘画:PhotoMaker Win11本地安装记录!

昨天介绍一个叫PhotoMaker的AI绘画开源项目。挺不错的&#xff01; 通过这个项目可以快速制作特定人脸的AI绘画作品&#xff0c;相比传统的技术效果会好很多&#xff0c;效率也高很多。 今天趁热打铁&#xff0c;本地电脑装装看&#xff0c;并且记录&#xff0c;分享一下&#…

城建档案数字化管理系统

城市建设档案数字化管理系统是指将城市建设相关档案纸质化资料转换为数字化形式&#xff0c;并通过信息技术手段进行存储、检索、管理和利用的系统。该系统旨在解决传统纸质档案管理存在的问题&#xff0c;提高档案管理的效率和准确性。 专久智能城市建设档案数字化管理系统主要…

自学C语言-7

第7章 循环控制 生活中总会有许多简单而重复的工作&#xff0c;为完成这些重复性工作&#xff0c;需要花费很多时间。使用循环语句来处理程序开发中简单、重复性的工作是最好不过的了。 本章致力于使读者了解while、do…while和for3种循环结构的特点&#xff0c;以及转移语句的…

Python第三方扩展库Matplotlib

Python第三方扩展库Matplotlib Matplotlib 是第三方库&#xff0c;不是Python安装程序自带的库&#xff0c;需要额外安装&#xff0c;它是Python的一个综合性的绘图库&#xff0c;提供了大量的绘图函数用于创建静态、动态、交互式的图形和数据可视化&#xff0c;可以帮助用户创…

Python实现时间序列分析AR定阶自回归模型(ar_select_order算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 时间序列分析中&#xff0c;AR定阶自回归模型&#xff08;AR order selection&#xff09;是指确定自回…

Springboot使用数据库连接池druid

springboot框架中可以使用druid进行数据库连接池&#xff0c;下面介绍druid在springboot中使用和参数配置介绍。 数据库连接池&#xff08;Druid&#xff09;是一种用于管理数据库连接的机制&#xff0c;其工作原理和常见使用方法如下&#xff1a; 原理&#xff1a;数据库连接…

Android 12 系统开机动画

一、查找Android系统提供的开机动画 在Android系统源码目录下输入 find ./ -name "bootanimation.zip" 如图所示 所输出的路劲即为bootanimation.zip所在路径&#xff0c;每个系统都不一样&#xff0c;建议用命令查找 二、复制到对应目录下 android12\out\target\…

vue使用mpegts.js教程

vue使用mpegts.js教程 最简单好用的H265网页播放器-mpegts.js简介特征受限性 使用步骤安装引入HTML 中添加视频标签video知识扩展 在容器里创建播放器 最简单好用的H265网页播放器-mpegts.js H265是新一代视频编码规范&#xff0c;与H264相比压缩比更高&#xff0c;同样的码率下…

Web实战丨基于Django的简单网页计数器

文章目录 写在前面Django简介主要程序运行结果系列文章写在后面 写在前面 本期内容 基于django的简单网页计数器 所需环境 pythonpycharm或vscodedjango 下载地址 https://download.csdn.net/download/m0_68111267/88795604 Django简介 Django 是一个用 Python 编写的高…

prism 10 for Mac v10.1.1.270激活版 医学绘图分析软件

GraphPad Prism 10 for Mac是一款专为科研工作者和数据分析师设计的绘图和数据可视化软件。以下是该软件的一些主要功能&#xff1a; 软件下载&#xff1a;prism 10 for Mac v10.1.1.270激活版 数据整理和导入&#xff1a;GraphPad Prism 10支持从多种数据源导入数据&#xff0…

设计与实现基于Java+MySQL的考勤发布-签到系统

课题背景 随着现代经济的迅速发展&#xff0c;电子考勤签到服务已经渗透到人们生活的方方面面&#xff0c;成为不可或缺的一项服务。在这个背景下&#xff0c;线上签到作为考勤签到的一种创新形式&#xff0c;为用户提供了便捷的操作方式&#xff0c;使得任务签到、个人签到记…

神经调节的Hebbian学习用于完全测试时自适应

摘要 完全测试时自适应&#xff08;Fully test-time adaptation&#xff09;是指在推理阶段对输入样本进行序列分析&#xff0c;从而对网络模型进行自适应&#xff0c;以解决深度神经网络的跨域性能退化问题。我们从生物学合理性学习中获得灵感&#xff0c;其中神经元反应是基…

【HarmonyOS应用开发】UIAbility实践第一部分(五)

一、UIAbility概述 1、UIAbility是一种包含用户界面的应用组件&#xff0c;主要用于和用户进行交互。UIAbility也是系统调度的单元&#xff0c;为应用提供窗口在其中绘制界面。 2、每一个UIAbility实例&#xff0c;都对应于一个最近任务列表中的任务。 3、一个应用可以有一个UI…

【学网攻】 第(14)节 -- 动态路由(EIGRP)

系列文章目录 目录 系列文章目录 文章目录 前言 一、动态路由EIGRP是什么&#xff1f; 二、实验 1.引入 实验步骤 实验拓扑图 实验配置 看到D开头是便是我们的EIGRP动态路由 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学…

公司宣传电子画册的制作方法

​制作公司宣传电子画册是一种非常有效的方式&#xff0c;可以展示公司的形象和产品&#xff0c;同时也可以吸引更多的潜在客户。不仅低碳环保&#xff0c;还省了不少人力和财力&#xff0c;只要一个二维码、一个链接就能随时随地访问公司的宣传画册。以下是一些制作电子画册的…

【vue】vue.config.js里面获取本机ip:

文章目录 一、效果&#xff1a;二、实现&#xff1a; 一、效果&#xff1a; 二、实现&#xff1a; const os require(os);function getLocalIpAddress() {const interfaces os.networkInterfaces();for (let key in interfaces) {const iface interfaces[key];for (let i …