list模拟

news2024/9/22 23:22:37

之前模拟了string,vector,再到现在的list,list的迭代器封装最让我影响深刻。本次模拟的list是双向带头节点的循环链表,该结构虽然看起来比较复杂,但是却非常有利于我们做删除节点的操作,结构图如下。 

 由于其节点结构特点,使得头插头删,尾插尾删的时间复杂度为O(1),因为我们只需要改变两边节点的指针链接即可。

节点类

    先来个开胃小菜,了解了解节点类,该类比较简单。由于每个节点既要存储数据,又要存储前后节点指针,所以我们封装一个结构体,用的struct而不是class, 因为我们在后面的函数需要访问节点存储的节点指针成员来达到遍历的效果,所以用的是struct。至于封装性,别人也不敢直接访问我节点存的指针变量,举个例子,如果他拿到了我的节点指针ptr,访问成员用ptr->+(成员名字),问题就在成员名字,每个人实现的类名函数名那都是五花八门,这就导致这段访问成员的代码就不具有平台移值性。

template <class T>  
	struct list_node
	{
		typedef list_node<T> node; 这里记忆一下node是节点类型名的重命名,
                                    后面还有很多重命名,免得搞混了。
		list_node(const T& val=T())
                              T()是一个匿名对象,当我们未传对象初始化时,
                              可用一个匿名对象来初始化value
			:_next(nullptr)
			, _prev(nullptr)   
                               用初始化列表来初始化,因为_val存的若是自定义类型
			, _val(val)        想要调用非默认构造函数得用初始化列表。 
		{
			;
		}
		node* _next;
		node* _prev;
		T _val;
	};

链表正向迭代器类:

  不知道还记不记得string和vector的迭代器,string的iterator就是char*的重命名,而vector则是T*的重命名,那为什么list的迭代器不能把node*直接typedef呢,然后begin()函数返回哨兵位的下一个节点地址,end()函数返回哨兵位节点地址。假设我们就这样实现list的迭代器,然后结合下面的使用场景。

my_list::list<int>::iterator it = l1.begin();
while (it != l1.end())
{
	it++;
}

   大家有没有想过it++到哪去了呢?是下一个节点吗?当然不是啦,链表的每个节点都是孤立的,你++怎么能到下一个节点呢?当时我立马就想到运算符重载了,在重载函数内部++指针是往后走,还是往前走,又或者是++到指针变成下一个节点地址那不都是我们实现者说了算。冷静,冷静,运算符重载首要原则就是操作数应该是自定义类型,所有类型的指针都是内置类型,所以节点指针它就没办法重载运算符,所以我们要把节点指针封装成类,这样我们才可以对这个类进行运算符重载,使得it++调用类内重载函数,到下一个节点。

template<class T,class Ref,class Ptr> Ref和Ptr这两个模板参数是为了实现const迭代器

	struct _list_iterator   使其成员函数可在类外访问
	{
		typedef list_node<T> node;
		
		typedef _list_iterator<T,Ref,Ptr> Self;

		_list_iterator(node* pnode=nullptr)//用一个node指针初始化其成员
			:_node(pnode)
		{
			;
		}
		//无析构函数,迭代器不能释放节点

		_list_iterator(const Self& l)//临时对象的引用,例如接受begin()
			:_node(l._node)
		{
			;
		}
		Self& operator++()//this指针是个迭代器类型的指针
		{
			_node = _node->_next;
			return (*this);
		}
		Self operator++(int)

       首先这两个++运算符重载还构成函数重载
       编译默认给前置++多传一个参数,使其调用这个函数,如果你把
      这两个函数参数换一下,前置++去掉int,后置++加上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& l)//要加const,因为外部可能是与end()比较
		{
			return (_node != l._node);
		}
		bool operator==(const Self& l)
		{
			return (_node == l._node);//判断迭代器内部的node指针是否相等
		}

    因为普通迭代器就一两个成员函数的返回值不同,比较简单的方法是用模板参数Ref
     控制返回const T&还是返回T&,

		Ref operator*()
		{
			return (_node->_val);//返回节点数据
		}

		//用类封装节点指针,使其可以重载++,*等运算符
		node* _node;
	};

我们实现的const迭代器并不是直接对迭代器类加个const,而是对特定的函数返回值做const修饰,所以迭代器成员函数无需加const.

 ->重载针对的是节点数据为自定义类型A,A类型存着一个变量_val,的时候,比如有一个类实例化对象为it, 我们希望重载->,使得下面的能调用到自定义类型内的变量,it->_val,但是it->返回的只是节点存的整个结构体A的地址,我们应该用it->->_val才访问到_val,而且这个重载也只能返回
地址,如果返回的是一个结构体对象,我们就要it->.(这有个点操作符)_val才能访问,更加奇怪,

但是当我们去测试的时候发现我们仅仅用it->_val即可访问,实际上是编译器会帮我们多加一个->,为了保证重载运算符的可读性,只能这样改了。还有如果节点存的数据成员是内置类型,我想应该是不可以用->重载的,你想返回的是int*, 但->必须是指向类的。

		Ptr operator->()const第三模板参数原因,控制返回const T*或T*,数据不可修改
                         
		{

			return &operator*();  

		}

反向迭代器类

  针对list链表,我们可以直接定义一个反向迭代器类叫reverse_iterator,反向迭代器中的成员函数只有++,--和正向迭代器不一样,所以看完正向迭代器的成员函数也就能理解反向迭代器的成员 *解引用重载都是返回节点数据,->重载都是调用operator*()重载再取地址。

namespace my_list
{
	template<class iterator, class Ref, class Ptr>
	class reverse_iterator
	{
	public:
		typedef reverse_iterator<iterator, Ref, Ptr> Self;
		reverse_iterator(iterator it)
			:_it(it)显示调用正向迭代器的拷贝构造
		{
			;
		}
		reverse_iterator(const Self& l)
			:_it(l._it)
		{
			;
		}
		Self& operator++()  复用正向迭代器_it的--函数
		{
			_it--;
			return (*this);
		}
		Self operator++(int)
		{
			Self tmp = (*this);
			_it--;
			return (tmp);
		}
		Self& operator--()   复用正向迭代器_it的++函数
		{
			_it++;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp = (*this);
			_it++;
			return tmp;
		}
		bool operator!=(const Self& l)
		{
			return (_it != l._it);  此处调用的是正向迭代器内部的比较
		}
		bool operator==(const Self& l)
		{
			return (_it == l._it);
		}
		Ref operator*()
		{
			iterator tmp = _it;
			return *(--tmp);
		}
		Ptr operator->()
		{
			return &operator*();
		}

	private:
		iterator _it;
	};
}

链表类

 我们知道目前设计的链表节点会保存前后位置的指针,这意味着链表类只需要管理头节点指针即可管理所有链表节点,有哨兵位则保存哨兵位地址,没有则保存第一个节点的地址。

template<class T>
	class list
	{
		typedef list_node<T> node;//设为私有,不让外部通过该处访问
	public:
	typedef _list_iterator<T, T&, T*> iterator;//设为公有
	typedef _list_iterator<T, const T&, const T*> const_iterator;

调用const迭代器就传const T&和const T*控制函数返回值不被修改就达到const迭代器作用了

    下面这两个迭代器的typedef顺序不能改,
不然可能会将reverse_iterator<const_iterator, const T&, const T*>中的reverse_iterator识别为
   已经typedef的reverse_iterator<iterator, T&, T*>
    
   typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
    typedef reverse_iterator<iterator, T&, T*> reverse_iterator;




      reverse_iterator rbegin()
		{
			return ((reverse_iterator)end());
		}
		const_reverse_iterator rbegin() const
		{
			return (const_reverse_iterator)end();
		}
		reverse_iterator rend()
		{
			return (reverse_iterator)begin();
		}
		const_reverse_iterator rend() const
		{
			return (const_reverse_iterator)begin();
		}
		iterator begin()
		{
			return _phead->_next;//返回匿名对象
		}
		iterator end()
		{
			return _phead;
		}
		const_iterator begin()const
		{
			return _phead->_next;//返回匿名对象
		}
		const_iterator end()const   要加const,要让const list<T>调用该函数,返回const迭代器
		{
			return _phead;
		}




		void Createhead()
		{
			_phead = new node;
			_phead->_next = _phead;
			_phead->_prev = _phead;
		}
		list()
		{
			Createhead();
		}
		list(const list<T>& l)//list的拷贝构造
		{
			Createhead();
			for (auto& c : l)
			{
				push_back(c);
			}
		}
		list(size_t n, const T& value = T())
		{
			Createhead();
			while (n--)
			{
				push_back(value);
			}
		}
		list(int n, const T& value = T())
		{
			Createhead();
			while (n--)
			{
				push_back(value);
			}
		}
		template<class inputiterator>
		list(inputiterator first, inputiterator last) 用一段迭代器区间初始化
		{
			Createhead();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		void swap(list<int>& lt)
		{
			std::swap(_phead, lt._phead);
		}
		list<T>& operator=(list<int> lt)//list<int>可用list代替
		{
			swap(lt);
			return *this;
		}
		~list()
		{
			clear();
			delete _phead;
			_phead = nullptr;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		void push_front(const T& value = T())
		{
			insert(begin(), value);
		}
		void push_back(const T& value = T())
		{
			//node* newnode = new node(value);
			//node* tail = _phead->_prev;
			链接新节点,注意链接关系
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _phead;
			//_phead->_prev = newnode;
			insert(end(), value);
		}
		iterator insert(iterator pos, const T& value = T())
		{
			node* newnode = new node(value);
			node* pose = pos._node;
			node* cur = pose->_prev;
			cur->_next = newnode;
			newnode->_prev = cur;
			newnode->_next = pose;
			pose->_prev = newnode;
			return newnode;
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		size_t size()const
		{
			int n = 0;
			iterator it = begin();
			while (it != end())
			{
				it++;
				n++;
			}
			return n;
		}
		bool empty()const
		{
			return size() == 0;
		}
		iterator erase(iterator pos)
		{
			node* pose = pos._node;//迭代器it是一个类,访问成员变量用.操作符
			pos._node = pos._node->_next;
			node* cur = pose->_prev;
			cur->_next = pose->_next;
			pose->_next->_prev = cur;
			delete pose;
			return pos;
		}
		
	private:
		node* _phead;
	};
}

上述我们封装了四个类,虽然一下子不知道如何说清楚为什么要分开描述,如果都挤在一起,那肯定是有点冗余,类本质是用来描述的,个人认为一种类应该是只描述一种对象的,如果一个类既描述链表节点,又描述链表,我感觉对于我后续的实现会很麻烦。

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

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

相关文章

收发存和进销存有什么区别?

一、什么是收发存和进销存 1、收发存 收发存是供应链管理中的关键概念&#xff0c;用于描述企业在供应链中的物流和库存管理过程。 收发存代表了企业在采购、生产和销售过程中的物流活动和库存水平。 收&#xff08;Receiving&#xff09; 企业接收供应商送达的物料或产品…

归并排序算法

归并排序 算法说明与实现代码&#xff1a; 归并排序&#xff08;Merge Sort&#xff09;: 归并排序是一种分治算法&#xff0c;它将列表分成两个子列表&#xff0c;分别进行排序&#xff0c;然后将排序好的子列表合并成一个有序列表。 package mainimport "fmt"fu…

手机商城免费搭建之java商城 开源java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框…

微信小程序代码优化3个小技巧

抽取重复样式 样式复用 我们会发现很多时候在开发的过程中会存在多个页面中都用到了同样的样式&#xff0c;那么其实之前有提到过&#xff0c;公用样式可以放在app.wxss里面这样就可以直接复用。 如&#xff1a;flex布局的纵向排列&#xff0c;定义在app.wxss里面 .flex-co…

win10安装cygwin

参考 Cygwin简介及其下载安装卸载_cygwin是什么软件_徐晓康的博客的博客-CSDN博客https://blog.csdn.net/weixin_42837669/article/details/114381405这个文章写的非常好&#xff0c;不过现在如果想安装多个包的话&#xff0c;采用gui的方式可以不行了&#xff0c;我采用的方式…

JavaScript 简单实现观察者模式和发布-订阅模式

JavaScript 简单实现观察者模式和发布-订阅模式 1. 观察者模式1.1 什么是观察者模式1.2 代码实现 2. 发布-订阅模式2.1 什么是发布-订阅模式2.2 代码实现2.2.1 基础版2.2.2 取消订阅2.2.3 订阅一次 1. 观察者模式 1.1 什么是观察者模式 概念&#xff1a;观察者模式定义对象间…

【Windows11】家庭版开启组策略指南

目录 背景新建一个cmd文件运行运行结果 背景 Win11找不到gpedit.msc怎么办&#xff1f;有用户通过命令窗口想要去打开本地组策略的时候&#xff0c;系统突然弹出了一个错误提示&#xff0c;显示系统缺少了gpedit.msc导致无法开启本地组策略编辑器了。那么这个情况要怎么去进行…

【Web开发指南】如何用MyEclipse进行JavaScript开发?

由于MyEclipse中有高级语法高亮显示、智能内容辅助和准确验证等特性&#xff0c;进行JavaScript编码不再是一项繁琐的任务。 MyEclipse v2023.1.2离线版下载 JavaScript项目 在MyEclipse 2021及以后的版本中&#xff0c;大多数JavaScript支持都是开箱即用的JavaScript源代码…

【Minio怎么用】Minio上传图片并Vue回显

流程&#xff1a; 目录 1.文件服务器Minio的安装 1.1 下载Minio安装后&#xff0c;新建1个data文件夹。并在该安装目录cmd 敲命令。注意不要进错目录。依次输入 1.2 登录Minio网页端 1.3 先建1个桶&#xff08;buckets&#xff09;&#xff0c;点击create a bucket 2. Spr…

前端小练-仿掘金导航栏

文章目录 前言项目结构导航实现创作中心移动小球消息提示 完整代码 前言 闲的&#xff0c;你信嘛&#xff0c;还得开发一个基本的门户社区网站&#xff0c;来给到Hlang,不然我怕说工作量不够。那么这个的话&#xff0c;其实也很好办&#xff0c;主要是这个门户网站的UI写起来麻…

操作系统_进程与线程(三)

目录 3. 同步与互斥 3.1 同步与互斥的基本概念 3.1.1 临界资源 3.1.2 同步 3.1.3 互斥 3.2 实现临界区互斥的基本方法 3.2.1 软件实现方法 3.2.1.1 算法一&#xff1a;单标志法 3.2.1.2 算法二&#xff1a;双标志法先检查 3.2.1.3 算法三&#xff1a;双标志法后检查 …

HarmonyOS/OpenHarmony元服务开发-卡片使用自定义绘制能力

ArkTS卡片开放了自定义绘制的能力&#xff0c;在卡片上可以通过Canvas组件创建一块画布&#xff0c;然后通过CanvasRenderingContext2D对象在画布上进行自定义图形的绘制&#xff0c;如下示例代码实现了在画布的中心绘制了一个笑脸。 Entry Component struct Card { private c…

如何把几个视频合并在一起?视频合并方法分享

当我们需要制作一个比较长的视频时&#xff0c;将多个视频进行合并可以使得整个过程更加高效。此外&#xff0c;合并视频还可以避免出现“剪辑断层”的情况&#xff0c;使得视频内容更加连贯&#xff0c;更加容易被观众理解和接受。再有&#xff0c;合并视频还可以减少视频文件…

第三方电容笔支持随手写吗?性价比高的触控笔推荐

在日常生活中&#xff0c;电容笔的用途非常广泛&#xff0c;无论是配上笔记本&#xff0c;还是配上ipad&#xff0c;又或者是配上手机&#xff0c;都是非常好用的办公利器。首先要明确自己的需要&#xff0c;然后才能选择适合自己的产品。苹果Pencil因为具有特殊的重力压感&…

数据结构07:查找[C++][顺序、分块、折半查找]

图源&#xff1a;文心一言 考研笔记整理~&#x1f95d;&#x1f95d; 在数据结构和算法中&#xff0c;查找是一种常见的操作&#xff0c;它的目的是在一个数据集合中找到一个满足条件的元素。本文将介绍三种常用的查找方法&#xff0c;分别是顺序查找、折半查找和分块查找~&a…

Unity实现在3D模型标记

Canvas 模式是UI与3D混合模式&#xff08;Render modelScreen space-Camera) 实现在3D模型标记&#xff0c;旋转跟随是UI不在3D物体下 代码&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public clas…

记一次sql注入分析与绕过【一】

下面是来自今天的项目&#xff0c;简单记录一下 手工注入 加单引号sql报错 sql语句如下&#xff0c;可见参数id原本未被引号包裹 SELECT DISTINCT u.* FROM t_user u WHERE u.name like %1% and u.account like %1% and u.state ? order by id desc limit 0,20 多方尝试…

warnings.filterwarnings(“ignore“) 是干嘛的

在python中运行代码经常会遇到的情况是——代码可以正常运行但是会提示警告 那么如何来控制警告输出呢&#xff1f;其实很简单&#xff0c;python通过调用warnings模块中定义的warn()函数来发出警告。我们可以通过警告过滤器进行控制是否发出警告消息 import warnings warnin…

数字工厂管理系统的实施步骤是什么

数字工厂管理系统是一种基于数字化技术和智能化设备的工厂管理系统&#xff0c;它可以实现工厂的全面、实时、动态管理&#xff0c;提高生产效率、降低成本、保证产品质量。实施数字工厂管理系统需要一系列的实施步骤&#xff0c;下面就数字工厂管理系统的实施步骤进行详细说明…

postgresql selected, no connection解决办法|armitage连接不上

postgresql selected, no connection 数据库没有连接&#xff0c;手动连接数据库即可。 手动连接数据库 msf > db_connect msf:admin127.0.0.1/msf 还是不行。 说明&#xff0c;数据库都连不上&#xff0c;先解决这个问题。 正文 看过很多&#xff0c;也试了很多&#xf…