[C++进阶[六]]list的相关接口模拟实现

news2024/11/13 19:12:39

1.前言

本章重点

在list模拟实现的过程中,主要是感受list的迭代器的相关实现,这是本节的重点和难点。

 2.list接口的大致框架

 list是一个双向循环链表,所以在实现list之前,要先构建一个节点类

template <class T>
struct ListNode
{
	T _val;
	ListNode* _prev;
	ListNode* _next;
	ListNode(const T& val = T())//构造函数
		:_prev(nullptr)
		, _next(nullptr)
		,_val(val)
	{}
};

节点中存储一个T模板类型的值和
上一个节点的地址和下一个节点的地址

在List类中,由于链表都是些链接关系,所以List类中的成员变量只需要定义一个那就是头节点head,知道head的链接关系。就能够知道list类对象中存放的内容!

template <class T>
class List
{
    public:typedef ListNode<T> Node;
    private:
        //带头节点的无参构造一个随机值
        void CreateHead()
        {
	    _pnode = new Node;
	    _pnode->_prev = _pnode;
	    _pnode->_next = _pnode;
        }
        Node* _pnode;
};

解释:给头节点pnode开辟一份空间后,头节点的指向最开始都是指向自身:

3.list的构造和析构函数

构造函数

此处介绍三种构造函数:空构造;构造n个值为val的节点;用迭代器区间来进行构造

无参构造:

//构造函数1
List()
{
	CreateHead();
}

构造n个值为val的节点

//构造函数2:创建n个值为val的节点
List(int n, const T& val = T())
{
	CreateHead();
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}

这个地方使用了push_back先用着,后续再实现

迭代器区间来进行构造:

//构造函数3:迭代器区间来进行构造
template <class InputerIterator>
List(InputerIterator first, InputerIterator last)
{
	CreateHead();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

ps:注意,这里不管是什么构造,在最开始时都需要构造一个带哨兵卫的头节点,所以说都要先使用CreateHead函数来进行构建。

析构函数

由于链表是一块一块的空间,通过某种形式把他连接起来。所以在析构函数时,要先把这些空间全部释放掉,然后再删除带哨兵卫的头结点。

//析构函数
void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}
~List()
{
	clear();
	delete _pnode;
	_pnode = nullptr;
}

这里使用到了迭代器来进行遍历,后续会实现迭代器相关的功能。

4.拷贝构造函数和赋值重载函数

拷贝构造函数

直接使用简单快速的写法来完成深拷贝。

	//拷贝构造函数
	List(const List<T>& node)
	{
		CreateHead();
		List<T> tmp(node.begin(), node.end());
		Swap(tmp);
	}
	void Swap(List<T>& node)
	{
		swap(_pnode,node._pnode);
	}

赋值重载函数

	//赋值重载函数
	List<T>& operator=(const List<T>& node)
	{
		List<T>tmp(node.begin(), node.end());
		Swap(tmp);
		return *this;
	}

这样的写法有两个精妙之处:

1.它先定义一个临时变量tmp来接受node的所有值,然后再将临时变量tmp
的pnode和拷贝的pnode交换,这样一来就完成了拷贝构造函数,并且tmp变量是构造函数初始化的,它是深拷贝,所以lt2对于lt1也是深拷贝。

2.tmp是临时变量,除了作用域会销毁,也就是出了此拷贝构造函数后会销毁,销毁时会调用析构函数,然而要构造的pnode以及和tmp的pnode交换了,所以tmp销毁时实际上是在帮原先的要构造的pnode销毁内存!

5.与容量有关的函数

size和empty

bool empty()
{
	return size() == 0;
}

size_t size()
{
	size_t sz = 0;
	iterator it =begin();
	while (it!= end())
	{
		sz++;
		it++;
	}
	return sz;
}

6.迭代器的模拟实现

其实仔细分析下来,发现链表是无法进行*和++,--以及->这些相关操作的。那么想让链表和迭代器一样进行++,--,*和->这些相关的操作,那么只需要封装一个类,把这些函数进行重写就可以了。

迭代器的大体结构如下:

template<class T>
struct List_iterator 
{
	typedef ListNode<T>* PNode;
    typedef List_iterator<T> Self;
	PNode _node;
};

构造函数

List_iterator(const PNode& node=nullptr)
	:_node(node)
{}

拷贝构造

//拷贝构造
List_iterator(const Self& l)
{
	_node=l._node;
}

ps:由于这里把迭代器相关类型重命名成了self,所以这里给出的就直接使用了self

++和--函数

Self& operator++()//前置++
{
	_node = _node->_next;
	return *this;
}
Self& operator--()//前置--
{
	_node = _node->_prev;
	return *this;
}
Self operator++(int) //后置++
{
	List_iterator<T> tmp(*this);
	_node = _node->_next;
	return tmp;
}
Self operator--(int) //后置--
{
	List_iterator<T> tmp(*this);
	_node = _node->_prev;
	return tmp;
}

==和!=函数

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

解引用和箭头->函数

迭代器的使用就像指针一样,所以解引用后应该直接得到节点的数据!

	T& operator*()
	{
		return _node->_val;
	}
	T* operator->()
	{
		return &(_node->_val);
	}

解释:解引用大家肯定都能理解。那么对用箭头->函数理解起来可能就有难度了。

举个例子来帮助我们理解

当list容器当中的每个结点存储的不是内置类型,而是自定义类型,例如日期类,那么当我们拿到一个位置的迭代器时,我们可能会使用->运算符访问Date的成员:

	list<Date> lt;
	Date d1(2021, 8, 10);
	Date d2(1980, 4, 3);
	Date d3(1931, 6, 29);
	lt.push_back(d1);
	lt.push_back(d2);
	lt.push_back(d3);
	list<Date>::iterator pos = lt.begin();
	cout << pos->_year << endl; //输出第一个日期的年份

注意: 使用pos->_year这种访问方式时,需要将日期类的成员变量设置为公有。

对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可。

T* operator->()
{
	return &_pnode->_val; //返回结点指针所指结点的数据的地址
}

讲到这里,可能你会觉得不对,按照这种重载方式的话,这里使用迭代器访问日期类当中的成员变量时不是应该用两个->吗?

这里本来是应该有两个->的,第一个箭头是pos ->去调用重载的operator->返回Date* 的指针,第二个箭头是Date* 的指针去访问对象当中的成员变量_year。

但是一个地方出现两个箭头,程序的可读性太差了,所以编译器做了特殊识别处理,为了增加程序的可读性,省略了一个箭头。

7.插入删除相关函数

有了迭代器之后插入删除相关函数就好实现了。

插入函数insert

//在pos位置前插入值为val的节点
iterator insert(iterator pos,const T&val)
{
	//创建节点
	Node* newnode = new Node(val);
	Node* cur = pos._node;
	Node* prev = pos._node->_prev;

	//开始插入
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	return iterator(newnode);
}

这里我实现的是在插入一个值之后,并把这个值的的迭代器位置返回去。

erase函数

//删除pos节点,然后返回下一个节点
iterator erase(iterator pos)
{
	assert(pos != end());
	Node* cur = pos._node;
	Node* next = pos._node->_next;
	Node* prev = pos._node->_prev;
	delete cur;
	prev->_next = next;
	next->_prev = prev;
	return iterator(next);
}

如果这里不返回一个合法的迭代器位置的话,那么就会有可能出现迭代器失效。

push_back和pop_back


push_back和pop_back函数分别用于list的尾插和尾删,在已经实现了insert和erase函数的情况下,我们可以通过复用函数来实现push_back和pop_back函数。

push_back函数就是在头结点前插入结点,而pop_back就是删除头结点的前一个结点。

	void push_back(const T& val)
	{
		insert(end(), val);
	}
	void pop_back()
	{
		erase(--end());
	}

8.总结

总的来说,list的底层实现较于vector来说要复杂一点,这其中的底层原因
就是list的迭代器还需要一层封装,而vector的迭代器不需要额外封装。但是在我们使用的角度来看,这两者并没有什么区别。

C++的强大就在于把复杂的底层全部封装起来了,而表面的使用上
list和vector并无太大区别,这就是C++封装的魅力!

 list模拟实现全部代码如下:

simulate_list/simulate_list · 青酒余成/初识数据结构 - 码云 - 开源中国 (gitee.com)

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

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

相关文章

Packet Tracer - 配置编号的标准 IPv4 ACL(两篇)

Packet Tracer - 配置编号的标准 IPv4 ACL(第一篇) 目标 第 1 部分&#xff1a;计划 ACL 实施 第 2 部分&#xff1a;配置、应用和验证标准 ACL 背景/场景 标准访问控制列表 (ACL) 为路由器 配置脚本&#xff0c;基于源地址控制路由器 是允许还是拒绝数据包。本练习的主要内…

华为OD机试 - 最大子矩阵 - 卡德恩算法(动态规划)(Java 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;E卷D卷A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加…

wav怎么转mp3格式?给你推荐几种音频格式转换方法

wav怎么转mp3格式&#xff1f;将wav文件转换为MP3格式是一个常见的操作&#xff0c;尤其适用于需要节省存储空间或确保文件兼容性的场景。wav文件保存了音频的所有原始数据&#xff0c;这使得它们的文件体积往往非常庞大。相比之下&#xff0c;MP3格式通过有损压缩技术显著减小…

费用管理系统如何优化企业年报台账归集流程?

随着企业规模的扩大和业务的复杂化&#xff0c;财务管理工作的重要性日益凸显。其中&#xff0c;年报台账归集作为财务管理的重要环节&#xff0c;不仅关乎企业财务数据的准确性和完整性&#xff0c;更直接影响到企业决策的科学性和合理性。面对海量的财务数据和复杂的归集要求…

算法训练——day15数组交集(是否去重)

349. 两个数组的交集 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的 交集。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,2,1], nums2 [2,2] 输出&#xff1a;[2]示例 2&#xff1a; 输入…

AI逻辑推理入门

参考数据鲸 (linklearner.com) 1. 跑通baseline 报名 申领大模型API 模型服务灵积-API-KEY管理 (aliyun.com) 跑通代码 在anaconda新建名为“LLM”的环境,并安装好相应包后,在jupyter notebook上运行baseline01.ipynb 2. 赛题解读 一般情况下,拿到一个赛题之后,我们需…

Python酷库之旅-第三方库Pandas(121)

目录 一、用法精讲 536、pandas.DataFrame.set_axis方法 536-1、语法 536-2、参数 536-3、功能 536-4、返回值 536-5、说明 536-6、用法 536-6-1、数据准备 536-6-2、代码示例 536-6-3、结果输出 537、pandas.DataFrame.set_index方法 537-1、语法 537-2、参数 …

Games101图形学笔记——着色

Shading Z-buffering&#xff08;深度缓冲&#xff09; Shading&#xff08;着色&#xff09;画家算法Z-BufferShading(着色&#xff09;Blinn-Phong Reflectance Model&#xff08;布林冯反射模型&#xff09;漫反射能量守恒 着色高光Blinn-Phong Reflection ModelShadingFreq…

Cpp输出多字符常量警告

Cpp输出多字符常量警告 Cpp中用单引号(single quotes)表示单个字符(single character)&#xff0c;例如a&#xff0c;$&#xff0c;用双引号(double quotes)表示字符串文本(text)&#xff0c;例如"Hello World! " 当在一个单引号里面存在多个字符时&#xff0c;Cpp…

怎么增加音频的音量?这几种方法可以轻松增加音频的音量!

怎么增加音频的音量&#xff1f;在日常生活的纷繁场景中&#xff0c;音频音量偏低的问题往往悄然成为我们不可忽视的困扰&#xff0c;它虽非重大难题&#xff0c;却能在关键时刻带来诸多不便与挑战&#xff0c;设想一下&#xff0c;在喧嚣的街头或拥挤的咖啡馆里&#xff0c;微…

ES分词导致查询结果不准确

问题现象 索引里面有数据&#xff0c;而没有查询出来。 如下图所示&#xff0c;术语库&#xff08;索引&#xff09;中里面有一条数据的原文是“层”&#xff0c;而根据完整的原文来查询该原文中的术语&#xff0c;并未将该术语查询出来。 根据原文查询该原文中的术语&#x…

FreeRTOS学习——接口宏portmacro.h

FreeRTOS学习——接口宏portmacro.h&#xff0c;仅用于记录自己阅读与学习源码 FreeRTOS Kernel V10.5.1 portmacro版本&#xff1a;GCC/ARM_CM7 portmacro.h是什么 portmacro.h头文件&#xff0c;用于定义与特定硬件平台相关的数据类型和常量。 在移植过程中&#xff0c;…

VulhubDC-4靶机详解

项目地址 https://download.vulnhub.com/dc/DC-4.zip实验过程 将下载好的靶机导入到VMware中&#xff0c;设置网络模式为NAT模式&#xff0c;然后开启靶机虚拟机 使用nmap进行主机发现&#xff0c;获取靶机IP地址 nmap 192.168.47.1-254根据对比可知DC-4的一个ip地址为192.1…

无人机光电吊舱的技术!!

1. 成像技术 可见光成像&#xff1a;通过高分辨率相机捕捉地面或空中目标的清晰图像&#xff0c;提供直观的视觉信息。 红外热成像&#xff1a;利用红外辐射探测目标的温度分布&#xff0c;实现夜间或恶劣天气条件下的隐蔽目标发现。 多光谱成像&#xff1a;通过不同波段的光…

日用百货小程序如何渠道经营开店

将货更多的卖出去是每位商家的心声&#xff0c;日用百货商家手中的货具备多样性&#xff0c;挑选的用户也多&#xff0c;由于货单价较低&#xff0c;因此不断获客并其多买/复购/留存/裂变等是长期发展的关键点。 如何获得更多经营渠道&#xff0c;线上找寻出路是方法之一&…

ROS和ROS2借助智能大模型的学习和研究方法

机器人相关知识的本身和价值-CSDN博客 知识本身在智能时代毫无价值&#xff0c;需要基于知识应用和创新才有价值。 学历报废并非来自扩招&#xff0c;而是智能模型的快速发展。-CSDN blink-领先的开发者技术社区 2024年中秋&#xff0c;智能模型实力已经如此&#xff0c;但还…

智算筑基,九章云极DataCanvas公司闪耀2024年服贸会

9月12日&#xff0c;2024年中国国际服务贸易交易会&#xff08;以下简称“服贸会”&#xff09;在北京隆重开幕&#xff0c;九章云极DataCanvas公司携AI智算产品系列深度参展本届服贸会&#xff0c;为观众奉上技术与应用深度融合的参展盛宴。 本届服贸会由中华人民共和国商务部…

文心一言 VS 讯飞星火 VS chatgpt (349)-- 算法导论23.2 8题

八、Borden教授提出了一个新的分治算法来计算最小生成树。该算法的原理如下:给定图 G ( V , E ) G(V,E) G(V,E)&#xff0c;将 V V V划分为两个集合 V 1 V_1 V1​和 V 2 V_2 V2​&#xff0c;使得 ∣ V 1 ∣ |V_1| ∣V1​∣和 ∣ V 2 ∣ |V_2| ∣V2​∣的差最多为1。设 E 1 E_…

2.使用 VSCode 过程中的英语积累 - Edit 菜单(每一次重点积累 5 个单词)

前言 学习可以不局限于传统的书籍和课堂&#xff0c;各种生活的元素也都可以做为我们的学习对象&#xff0c;本文将利用 VSCode 页面上的各种英文元素来做英语的积累&#xff0c;如此做有 3 大利 这些软件在我们工作中是时时刻刻接触的&#xff0c;借此做英语积累再合适不过&a…

【每日刷题】Day124

【每日刷题】Day124 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. LCR 079. 子集 - 力扣&#xff08;LeetCode&#xff09; 2. 1863. 找出所有子集的异或总和再求和 …