【C++】深度解析:用 C++ 模拟实现 list 类,探索其底层实现细节

news2024/12/25 15:02:21

 

目录

list介绍

list模拟实现

list 节点类

list 的迭代器

定义 

构造函数

解引用

operator前置++和--与后置++和--

operator==与operator!=

list 类

构造函数

 begin()和end()

 拷贝构造

erase()

clear()

析构函数

insert

 push_back 和 push_front

pop_back 和 pop_front

完整代码


 

⭐list介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)。

⭐list模拟实现

  1. list的底层是双向链表结构,包含有一个哨兵节点。
  2. 模拟实现list,要实现下列三个类:
  •         ①list节点类
  •         ②迭代器的类
  •         ③list主要功能的类(size(),empty()...)

模拟实现list的类的基本功能(增删等操作)要建立在迭代器类和节点类均已实现好的情况下才得以完成。

✨list 节点类

  • 定义list中的节点ListNode,包含前驱指针,后驱指针和数据变量;
  • 使用struct而不使用class定义类,是为了方便访问每个一个节点 ,struct默认是pbulic,而class中成员变量要定义为private,不方便访问。
	template<class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _data;

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

✨list 的迭代器

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

1. 原生态指针,比如:vector

2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

  1. 指针可以解引用,迭代器的类中必须重载operator*()
  2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
  3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
  4. 至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载--
  5. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

📖定义 

	template<class T,class Ref,class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T,Ref,Ptr> Self;

		//内置类型 指针
		Node* _node;
    }
  • 可以发现这里list的模板含有三个类型参数,这样做是为了方便让编译器能依照模板自动生成一个const迭代器,而不需要我们手动写。
  • 两个参数名Ref(reference:引用)和Ptr(pointer:指针),见名知义。

📖构造函数

		ListIterator(Node* node)
			:_node(node)
		{}

 迭代器指向所传节点

📖解引用

//*it 解引用
//T& operator*()
Ref operator*()
{
	return _node->_data;
}
//it->
//T* operator->()
Ptr operator->()
{
	return &_node->_data;
}
  • 重载 * ,解引用就可以直接访问到节点里面的数据data 
  • 如果访问的数据是Date类型的,那么重载 -> 就可以访问到Date类里面的_year、_day等(如果是Date类,那data就说Date类里面的数据) 

📖operator前置++和--与后置++和--

//前置++
Self& operator++()
{
	_node = _node->_next;
	return *this;
}
//后置++
Self operator++(int)
{
	Self tmp(*this);
	_node = _node->_next;
	return tmp; // tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用
}
//前置--
Self& operator--()
{
	_node = _node->_prev;
	return *this;
}
//后置--
Self operator--(int)
{
	Self tmp(*this);
	_node = _node->_prev;
	return tmp;// tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用
}

注:

  • 这里值得注意的是,为了区分前置和后置,我们会在后置的重载函数中传缺省值int,从而与前置构成重载 
  • 局部变量不能返回引用

📖operator==与operator!=

bool operator!=(const Self& it)
{
	return _node != it._node;
}
bool operator==(const Self& it)
{
	return _node == it._node;
} 
  • 这个比较的就是两个迭代器中指向的节点的地址是否相等,从而可以判断迭代器是否指向了end() 。

✨list 类

template<class T>
class list
{
	 typedef ListNode<T> Node;

public :

	typedef ListIterator<T, T&, T*> iterator;
	typedef ListIterator<T,const T&,const T*> const_iterator;
private:

	Node* _head;
	size_t _size;
}
  • 迭代器写成三个参数类型的模板,可以让编译器生成两个类,一个普通的iterator和一个const_iterator
  • const_iterator 只能读取它所指向的元素,不能修改。

📖构造函数

//带头双向循环链表的构造函数
list()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
}
  • 初始化时,new一个头节点,然后使这个头节点的前后指针都指向自己 

📖begin()和end()

iterator begin()
{
	return _head->_next;//也可以直接这么写,进行隐式类型转换(单参数的构造函数支持隐式类型转换)
}
iterator end()
{
	return iterator(_head);//匿名对象,局部变量 不能返回引用
}
//const迭代器需要的是迭代器指向的内容不能修改
//const iterator 是迭代器本身不能修改
const_iterator begin() const
{
	return _head->_next;
}
const_iterator end()const
{
	return const_iterator(_head);
}
  • 返回时使用了匿名对象,不用实例化一个新的对象
  • 不能引用返回的匿名对象
  • const迭代器指向的内容不能修改 

 📖拷贝构造

//lt2(lt)
list(const list<T>& lt) 
{
	empty_init();
	for (auto& e : lt)
	{
		push_back(e);
	}	
}
  • list的每个节点不连续,需要一个个拷贝 
  • 需要析构的时候,一般就需要自己写深拷贝

📖erase()

iterator erase(iterator pos)
{
	//prev cur next
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	_size--;
	prev->_next = next;
	next->_prev = prev;
	delete cur;
	return iterator(next);//匿名对象,局部变量 不能返回引用
}
  • delete删除节点,每一个节点都是动态开辟出来的

  • 返回被删除元素后面一个元素的迭代器位置

📖clear()

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
	//不清除头节点 
}
  • erase之后,it更新到下一个节点的位置继续erase
  • clear()不删除头节点

📖析构函数

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}
  • clear()删除除去头节点以外的所有节点
  • delete删除头节点

📖insert

void insert(iterator pos, const T& val)
{
	Node* cur = pos._node;
	Node* newnode = new Node(val);
	Node* prev = cur->_prev;
	_size++;
	//prev newnode cur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
}
  • 插入到pos位置之前 

 📖push_back 和 push_front

void push_back(const T& x)
{
	insert(end(), x);
}
void push_front(const T& x)
{
	insert(begin(), x);
}
  • 复用insert ()

📖pop_back 和 pop_front

void pop_back()
{
	erase(--end());
}
void pop_front()
{
	erase(begin());
}
  • 复用erase() 

✨完整代码

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 Ref,class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T,Ref,Ptr> Self;

		//内置类型 指针
		Node* _node;

		ListIterator(Node* node)
			:_node(node)
		{}

		//*it 解引用
		//T& operator*()
		Ref operator*()
		{
			return _node->_data;
		}
		//it->
		//T* operator->()
		Ptr operator->()
		{
			return &_node->_data;
		}
		//前置++
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		//后置++
		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp; // tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用
		}

		//前置--
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//后置--
		Self operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;// tmp 是一个局部变量,它在函数返回后将不再存在,所以不能返回引用
		}

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

	template<class T>
	class list
	{
		 typedef ListNode<T> Node;

	public :
		//typedef ListIterator<T> iterator;
		//typedef ListConstIterator<T> const_iterator;//重新写一个ListConstIterator类(这个方法比较冗余)

		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T,const T&,const T*> const_iterator;//写成模板,让编译器生成两个类,而不是我们自己写
		/*iterator begin()
		{
			return iterator(_head->_next);
		}*/
		iterator begin()
		{
			return _head->_next;//也可以直接这么写,进行隐式类型转换(单参数的构造函数支持隐式类型转换)
		}
		iterator end()
		{
			return iterator(_head);//匿名对象,局部变量 不能返回引用
		}
		//const迭代器需要的是迭代器指向的内容不能修改
		//const iterator 是迭代器本身不能修改
		const_iterator begin() const
		{
			return _head->_next;
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}
		void empty_init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			_size = 0;
		}
		list()
		{
			//this->empty_init();
			empty_init();
		}
		//lt2(lt)
		list(const list<T>& lt) 
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		//需要析构,一般就需要自己写深拷贝
		void swap(list<T>& lt)
		{
			//lt是局部变量
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}
		//lt1 = lt3
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
			//不清除头节点 
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		/*void push_back(const T& x)
		{
			Node* newnode = new Node(x);
			Node* tail = _head->_prev;
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}*/
		void push_back(const T& x)
		{
			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		void insert(iterator pos, const T& val)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(val);
			Node* prev = cur->_prev;
			_size++;
			//prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
		}
		iterator erase(iterator pos)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			_size--;
			prev->_next = next;
			next->_prev = prev;
			delete cur;
			return iterator(next);//匿名对象,局部变量 不能返回引用
		}
		size_t size()const
		{
			return _size;
		}
		bool empty()
		{
			return _size == 0;
		}
	private:
		Node* _head;
		size_t _size;
	};

____________________

⭐感谢你的阅读,希望本文能够对你有所帮助。如果你喜欢我的内容,记得点赞关注收藏我的博客,我会继续分享更多的内容。⭐

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

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

相关文章

CTF-pwn-虚拟化-vmmware 前置

文章目录 参考vmware逃逸简介虚拟机和主机通信机制(guest to host)共享内存&#xff08;弃用&#xff09;backdoor机制Message_Send和Message_RecvGuestRPC实例RpcOutSendOneRawWork实例 vmware-rpctool info-get guestinfo.ip各个步骤对应的backdoor操作Open RPC channelSend …

数据结构初阶(c语言)-双向链表

这里首先纠正上篇文章一个错误&#xff0c;链表的一个有效数据点应该称为结点而不是节点。 一&#xff0c;双向链表的概念与结构 1.1概念与结构示意图 我们所说的双向链表全称为带头双向循环链表&#xff0c;也就是说此链表带有哨兵位结点(不存放任何数据的结点&#xff0c;且…

Oauth2协议的四种模式

B站视频 概念 Oauth2.0&#xff08;Open Authorization&#xff09; 一个关于授权的开放网络标准 允许用户授权第三方应用访问用户存储在其他服务提供者上的信息 不需要将用户名和密码提供给第三方应用 Oauth2中的各个角色 授权码模式 第一步 获取授权码 以上流程中的授…

产品经理NPDP好考吗?

NPDP是新产品开发专业人员的资格认证&#xff0c;对于希望在产品管理领域取得认可的专业人士来说&#xff0c;NPDP认证是一项重要的资格。 那么&#xff0c;产品经理考取NPDP资格认证究竟难不难呢&#xff1f; 首先&#xff0c;NPDP考试的难易程度取决于考生的背景和准备情况…

通信类IEEE会议——第四届通信技术与信息科技国际学术会议(ICCTIT 2024)

[IEEE 独立出版&#xff0c;中山大学主办&#xff0c;往届均已见刊检索] 第四届通信技术与信息科技国际学术会议&#xff08;ICCTIT 2024&#xff09; 2024 4th International Conference on Communication Technology and Information Technology 重要信息 大会官网&#xf…

C#调用OpenCvSharp实现图像的角点检测

角点检测用于获取图像特征&#xff0c;以支撑运动检测、目标识别、图像匹配等方面的应用。常用的角点检测算法包括Kitchen-Rosenfeld算法、Harris算法、KLT算法、SUSAN算法等&#xff0c;本文学习并测试Harris角点检测算法。   关于Harris算法的数学原理请见参考文献1的第18、…

解开基于大模型的Text2SQL的神秘面纱

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

【Unity国产化信创平台】虚拟机VMware Workstation Pro虚拟机下载安装

目录 一、虚拟机软件VMware Workstation Pro下载 二、虚拟机安装流程 1.傻瓜式安装 2.是否自动安装WHP 一、虚拟机软件VMware Workstation Pro下载 https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion 官网各种访问出错&#xff0c;下载界面总是…

linux禁用root

linux禁用root 1. 禁止普通用户切换到root1.1 sudo -i和sudo -s的区别1.2 sudo -i和直接登录root账号的区别1.3 禁止sudo -i切换root1.4 禁止su - root切换root 2. 禁止root远程登录2.1 ssh禁止root登录2.2 禁止远程桌面登录 本文主要介绍&#xff1a; 如何禁止普通用户切换到r…

划重点!PMP报考条件、报考步骤、考试内容、适合人群

参加PMP认证的好处&#xff0c;可以从几个方面来认识&#xff1a; 一、参加PMP认证与考试的过程&#xff0c;同时是一个系统学习和巩固项目管理知识的过程 二、参加PMP认证&#xff0c;您可以获得由PMI颁发的PMP证书 而拥有PMP认证表示你已经成为一个项目管理方面的专业人员…

基于微信小程序的健康饮食系统/健康饮食管理系统

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的小程序应运而生&#xff0c;各行各业相继进入信息管理时代&…

ST Stellar-E SR5E1 22KW OBC combo 3KW DC-DC汽车充电器解决方案

对于全球的环境保护意识抬头&#xff0c;全球的汽车产业慢慢步入电动化的时代&#xff0c;以减少碳排放。整车系统主要是由电池、电驱、电控的三电所构成&#xff0c;其中电池系统是整车的动力来源&#xff0c;而对电池充电的OBC系统更甚重要。一具高度安全性且高效的OBC系统&a…

a bean of type ‘org.redisson.api.RedissonClient Springboot 集成Redisson 配置

Springboot 集成 在导入redisson坐标之后 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.17.5</version></dependency> 使用注解注入示例 Resource private RedissonClient re…

QML鼠标右键菜单

原理非常简单&#xff0c;先写个Menu&#xff0c;通过MouseArea来填充需要右键菜单的区域。然后在onClicked信号中&#xff0c;让Menu的x、y等于当前鼠标的x、y&#xff0c;最后用调用Menu的open()方法将菜单在该位置显示出来。 import QtQuick import QtQuick.Controls.Mater…

【JDY-10M】蓝牙MESH

目录 一、实物图 二、原理图 引脚定义 三、简介 基本原理 产品特点 产品应用范围 出厂默认配置 应用 1&#xff0c;可直接与手机进行连接&#xff0c;进行数据的传输和控制 2&#xff0c;可蓝牙与蓝牙之间进行连接&#xff0c;可进行MESH组网 四、尺寸 五、注意 源文件下载 可访…

基于Jeecgboot3.6.3的vue3版本的流程ProcessViewer的修改

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、因为之前ProcessViewer是vue2的组件版本&#xff0c;平时显示也还正常&#xff0c;但在历史记录的时候老是出现下面的问题。 就是第一次进去在panel点击流程图的时候不会出现&#xff0…

React开发者并不存在

根本就没有所谓的React开发者 — 永远不要这样称呼自己。 这是许多软件开发者犯的一个巨大错误&#xff0c;浪费了你大量时间。 专注于工具而非概念。忽视了大局。 React只是一个JavaScript工具。JavaScript只是一个计算工具。计算只是一个解决问题的工具。 当我刚开始编码时&a…

VulnHub:cengbox1

靶机下载地址&#xff0c;下载完成后&#xff0c;用VirtualBox打开靶机并修改网络为桥接即可搭建成功。 信息收集 主机发现和端口扫描 扫描攻击机&#xff08;192.168.31.218&#xff09;同网段存活主机确认目标机ip&#xff0c;并对目标机进行全面扫描。 nmap 192.168.31.…

【QT】事件分发器 事件过滤器

qt 系统 - 事件分发器 and 事件过滤器 一、事件分发器1. 事件分发器概念2. 事件分发器工作原理 二、事件过滤器 一、事件分发器 1. 事件分发器概念 在 Qt 中&#xff0c;事件分发器(Event Dispatcher) 是一个核心概念&#xff0c;用于处理 GUI 应用程序中的事件。事件分发器负…

Java黑色界面陪玩高端小程序源码陪练APP源码H5公众号源码电竞系统

&#x1f680;【电竞新纪元】解锁高端陪玩小程序源码 & 陪练APP秘籍&#xff0c;H5公众号全攻略&#xff01; &#x1f3ae; 开篇&#xff1a;电竞热潮下的新机遇 Hey游戏迷们&#xff01;随着电竞行业的蓬勃发展&#xff0c;你是否也想在这股浪潮中分得一杯羹&#xff1…