C++——list类及其模拟实现

前言:这篇文章我们继续进行C++容器类的分享——list也就是数据结构中的链表,而且是带头双向循环链表


一.基本框架

namespace Mylist
{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 list{typedef ListNode<T> Node;public://构造函数list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}//析构函数~list(){iterator it = begin();while (it != end()){it = erase(it);}delete _head;_head = nullptr;}//数据个数size_t size(){iterator it = begin();size_t Size = 0;while (it != end()){Size++;it++;}return Size;}private:Node* _head;};
}

由于要满足存储任意类型的数据,所以我们必须要使用模版来进行定义。 


迭代器

关于list类中的最难之处,就是迭代器了。

因为迭代器的原理即为指针,对于string和vector这种创建的对象的物理空间是连续的类来说,我们可以直接对迭代器进行“++”、“--”等数学运算

而对于本质为链表的list来说,由于每个节点的物理空间都是随机创建,各个节点的地址并不连续,所以我们没法直接进行迭代器的数学运算,而需要对迭代器的各种功能进行重新定义,所以我们创建一个专门为迭代器服务的类

	//迭代器template<class T>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T> Self;ListIterator(Node* node):_node(node){}//解引用T& operator*(){return _node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}//后置++Self operator++(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& it){return _node != it._node;}//相等bool operator==(const Self& it){return _node == it._node;}Node* _node;};

随后在list类中将该类名重定义为iterator,便可正常使用迭代器了

		typedef ListIterator<T> iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}

 这里值得注意的是,因为是带头双向循环链表,所以链表的开始即哨兵位的下一个,而结尾就是哨兵位


但是现在的迭代器是存在问题的,它并不能实现对const修饰的数据的操作,所以我们还需要一个const迭代器。因为我们的普通迭代器就是用模版来实现的,所以这里可以直接通过模版来实现const迭代器

	//迭代器template<class T,class Ref>struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T,Ref> Self;//构造函数ListIterator(Node* node):_node(node){}//解引用Ref operator*(){return _node->_data;}//前置++Self& operator++(){_node = _node->_next;return *this;}//后置++Self operator++(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& it){return _node != it._node;}//相等bool operator==(const Self& it){return _node == it._node;}Node* _node;};
		typedef ListIterator<T,T&> iterator;typedef ListIterator<T,const T&> const_iterator;

这里有一个细节,因为T同时还要服务于Node类,所以不能直接对其进行修改,而是另用一个模版参数。 

 因为const对象与非const对象最大的不同之处在于对数据的访问,所以定义一个名为Ref(引用)的模版参数,来对解引用运算符重载函数进行改造


二.常用操作

1.插入

先来看任意位置的插入需要传入某个位置的指针pos

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

 现在对于我们来说就是非常简单,测试如下:

这里有一个小细节,如果我们插入的位置是第一个节点之前,由于it迭代器的指向并未改变,所以如果进行遍历,他就不会遍历出我们新插入的数据,所以需要更新一下it。


有了pos位置的插入之后,就可以用它来扩展头插和尾插

		//尾插void push_back(const T& x){insert(end(), x);}//头插void push_front(const T& x){insert(begin, x);}

2.删除

我们同样先写出pos位置的删除

		//pos删除iterator erase(iterator pos){Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;next->_prev = prev;prev->_next = next;delete cur;return iterator(next);}

由于删除会导致迭代器成为野指针,所以我们要对其进行更新, 测试如下:


同样由其扩展出头删和尾删

		//头删void pop_front(){erase(begin());}//尾删void pop_back(){erase(--end());}

3.拷贝

和string和vector一样,list的拷贝也需要使用深拷贝,那么它的拷贝构造函数该怎么写?

同样是要开辟新的空间,需要一个自己的头结点,随后按照被拷贝的链表的数据进行尾插即可:

		//拷贝构造list(const list<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;for (auto& e : lt){push_back(e);}}

测试如下:


此外,还有“=”运算符重载的方法:

		//交换void swap(list<T>& it){std::swap(_head, it._head);}//=运算符重载list<T>& operator=(list<T> lt){swap(lt);return *this;}

这里仍然是巧妙的运用swap函数,因为lt是一个临时拷贝,有自己的空间和地址,所以直接让两者进行交换,lt在退出函数时即被销毁,而拷贝者则继承了它的地址空间,测试如下:


总结

关于list类的基本知识就分享到这里啦。

因为与string和vector都存在很多相似之处,所以建议将这三者放在一起学习。

喜欢本篇文章记得一键三连,下期再见!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【重学C语言】三、C语言最简单的程序

【重学C语言】三、C语言最简单的程序 最简单的程序头文件使用尖括号 < >使用双引号 ""区别与注意事项示例 主函数认识三个错误 常量和变量常量ASCII 码表转义字符 关键字数据类型关键字存储类关键字修饰符关键字控制流程关键字函数相关关键字其他关键字 变量变…

Linux 恶意软件“Migo”针对 Redis 进行加密劫持攻击

安全研究人员遇到了一种新的加密劫持活动&#xff0c;该活动使用一种名为 Migo 的新恶意软件&#xff0c;该恶意软件针对 Linux 主机上的 Redis 服务器。在 Cado Security 研究人员注意到在野外利用 Redis 系统的新命令后&#xff0c;该活动曝光了。 初始访问 根据 Cado secu…

RUST语言基本数据类型认识

1.RUST的基本数据类型参考: 2.使用RUST数据类型声明变量并赋值: let a:i8=1;//8位有符号整数let a1:u8=2;//8位无符号整数let b:i16=1;//16位有符号整数let b1:u16=2;//16位无符号整数let c:i32=1;//32位有符号整数let c1:u32=2;//32位无符号整数let d:i64=1;//64位有符号整数l…

公众号爆文策略与实践:揭秘千万阅读量的秘密

1. 引言 介绍公众号爆文的重要性&#xff0c;以及分享个人通过每天投入半小时赚到30倍门票的经验。强调跟上大佬步伐&#xff0c;提升认知的重要性。 2. 爆文的底层逻辑 2.1 推荐的底层逻辑 内容分发机制的变化&#xff0c;从仅限于直接关注到通过搜索、浏览推荐等多种方式…

【项目实战经验】DataKit迁移MySQL到openGauss(上)

前言 本文将分享DataKit迁移MySQL到openGauss的项目实战&#xff0c;供广大openGauss爱好者参考。 1. 下载操作系统 https://www.openeuler.org/zh/download https://support.huawei.com/enterprise/zh/doc/EDOC1100332931/1a643956 https://support.huawei.com/enterprise…

封装一个vue3的公共组件

在Vue 3中&#xff0c;封装公共组件的场景包括但不限于以下几种情况&#xff1a; 重复使用的组件&#xff1a;如果你发现某个组件在多个地方重复使用&#xff0c;那么将其封装成公共组件是很有意义的。比如&#xff0c;页面中的各种表单控件&#xff08;输入框、下拉框、日期选…

硬件开发文档规范

本文出发点&#xff1a; 一般来说&#xff0c;越是大公司越注重开发文档的规范性&#xff0c;因为这样最大的好处是能够保证开发的连贯性&#xff0c;也就是即使有员工离职了&#xff0c;只要开发文档是齐全的&#xff0c;新员工入职后&#xff0c;就能够很快接手工作&#xf…

Linux驱动学习:从Linux主机nfs共享文件到uboot

第一步&#xff1a;在Linux主机上开启NFS服务&#xff0c;使用如下命令安装NFS服务&#xff1a; sudo apt-get install nfs-kernel-server rpcbind 第二步&#xff1a;创建一个文件夹用于共享&#xff0c;直接以nfs命名就行&#xff1a; 第三步&#xff1a;打开nfs服务配置文…

arm裸机(1)、点灯|按键

芯片是S3C2440 首先看原理图&#xff0c;led_1234分别对应引脚GPB 5678 设置引脚为输出 向寄存器相应位写入 #define GPBCON (*(volatile unsigned long *)0x56000010) //p5 6 7 8 void led_init(void) {GPBCON & ~(0x3 << 10);GPBCON | (0x1 <<…

LeetCode-19. 删除链表的倒数第 N 个结点【链表 双指针】

LeetCode-19. 删除链表的倒数第 N 个结点【链表 双指针】 题目描述&#xff1a;解题思路一&#xff1a;双指针解题思路二&#xff1a;优化解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。…

MTU/TCPMSS/VLAN/ACCESS/TRUNK/HYBRID

MTU RFC标准定义以太网的默认MTU值为1500 最小64字节是为了保证最极端的冲突能被检测到&#xff0c;64字节是能被检测到的最小值&#xff1b;最大不超过1518字节是为了防止过长的帧传输时间过长而占用共享链路太长时间导致其他业务阻塞。所以规定以太网帧大小为64~1518字节&am…

构建第一个JS应用(FA模型)

创建JS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。选择Application应用开发&#xff08;本文以应用开发为例&#xff0c;Atomic Service对…