【C++初阶】list的模拟实现 附源码

一.list介绍

list底层是一个双向带头循环链表,这个我们以前用C语言模拟实现过,->双向带头循环链表

下面是list的文档介绍: list文档介绍

我们会根据 list 的文档来模拟实现 list 的增删查改及其它接口。


 

二.list模拟实现思路

既然是用C++模拟实现的,那么一定要封装在类里

为了适合各种类型的数据,会使用模板

节点 Node

了解双向循环带头链表的都知道,我们需要一个节点 (Node),之前用C语言实现的时候,我们写了一个叫做 BuynewNode 的函数来获取节点,而在C++里我们用类封装一个,注意这个用 struct 封装比较好,因为 struct 默认是公有的,这样方便我们访问,所以可以写一个类:

    struct  list_node

迭代器  iterator

我们知道,C++提供了一种统一的方式来访问容器,这就是迭代器,string 和 vector 的迭代器模拟实现很简单,因为 string 和 vector 底层是用数组实现的,数组是一段连续的物理空间,支持随机访问,所以它是天然的迭代器

但是链表不一样,它不是一段连续的物理空间,不支持随机访问,所以想让 list 的迭代器在表面上和 string,vector 的迭代器用起来没有区别,我们在底层上就需要用类封装迭代器,然后再迭代器类的内部,重载  ++  --  *  ->  !=  ==  这些迭代器会用到的运算符

所以创建一个迭代器类:

   struct  list_iterator

const 迭代器  const_iterator

实现的普通的迭代器,还有 const 迭代器,const 迭代器的意思是让指针指向的内容不变,而指针本身可以改变,例如指针++,指针-- 这种操作,所以 const 迭代器与普通迭代器的不同只有 重载 * 运算符的返回值不同,它是 const  T&  (T是模板参数),重写一个const 迭代器类又显得太冗余,代码的可读性就降低了;

前面在学习模板时,我们知道不同的模板参数,编译器会生成不同的函数,所以我们选择加一个模板参数 :Ref 。这样只要在显示实例化模板参数时:

              普通迭代器就传 T&

              const 迭代器就传 const T&

-> 运算符重载

看下面这段代码:

using namespace std;struct A
{A(int a1,int a2):_a1(a1),_a2(a2){}int _a1;int _a2;
};void test_list()
{list<A> lt;   //实例化自定义类型lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a1 << " " << it->_a2 << endl;  //像指针一样访问自定义类型里的成员变量it++;}	
}int main()
{test_list();return 0;
}

有时候,实例化的模板参数是自定义类型,我们想要像指针一样访问访问自定义类型力的成员变量,这样显得更通俗易懂,所以就要重载 -> 运算符,它的返回值是 T* ,但是正常来说这里应该是这样访问的: it -> -> _a1

因为迭代器指向的是 整个自定义类型,要想再访问其内部成员应该再使用一次 -> (这个->就不是重载的 -> ,就是普通的 -> ),但是上面的代码为什么就写了一个 -> ,这个是C++语法把这里特殊化了。

那该怎么在迭代器类里重载 -> 运算符呢?

和const 迭代器一样,只需要再加一个模板参数 :Ptr

显示实例化的时候传 T* 就行了。 

迭代器类 模拟实现源码: struct list_iterator

以上的都算 list 模拟实现的难点,其他的像 重载 ++ 什么的,对于学过数据结构的小伙伴们是非常简单的,就不赘述了,没学过的可以看看这篇文章:双向带头循环链表

template<class T,class Ref,class Ptr>   //三个模板参数struct list_iterator   //封装迭代器{typedef list_node<T> Node;  //重命名节点typedef list_iterator<T, Ref, Ptr> self;  //重命名迭代器类型Node* _node;list_iterator(Node*node)   //构造函数,单参数的构造函数支持隐式类型转换:_node(node){}//重载 * ++ -- != == ->Ref operator*() const{return _node->_val;}Ptr operator->() const{return &_node->_val;}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& lt) const{return _node != lt._node;}bool operator==(const self& lt) const{return _node == lt._node;}};

list

我们在用C语言实现双向带头循环链表时,会先初始化链表的头(head),即让它的 前驱指针(prev)和后继指针(next)都指向自己

在C++的模拟实现 list 中,我们会创建一个类 list  来管理链表的节点并实现增删查改及其它接口,所以 list  的构建函数就是初始化 头(head)节点


三.源码

list.h

 

我们可以模拟实现以上接口,具体函数的逻辑可以查阅文档,实现起来都是很简单的。

namespace nagi   //把模拟实现list的类都放在一个命名空间里封装起来
{template<class T>struct list_node   //创建节点{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T())  //构造函数,初始化节点:_prev(nullptr),_next(nullptr),_val(val){}};template<class T,class Ref,class Ptr>   //三个模板参数struct list_iterator   //封装迭代器{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> self;Node* _node;list_iterator(Node*node)   //构造函数,单参数的构造函数支持隐式类型转换:_node(node){}//重载 * ++ -- != == ->Ref operator*() const{return _node->_val;}Ptr operator->() const{return &_node->_val;}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& lt) const{return _node != lt._node;}bool operator==(const self& lt) const{return _node == lt._node;}};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迭代器void empty_init()  //因为构造函数和拷贝构造都会初始化头节点,所以就写成一个函数了{_head = new Node;_head->_prev = _head;_head->_next = _head;_sz = 0;}list()  //构造函数{empty_init();}//普通迭代器iterator begin(){return _head->_next;}iterator end(){return _head;}//const迭代器const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}iterator insert(iterator pos, const T& x)  //在pos之前插入{Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_sz++;return newnode;}iterator erase(iterator pos)   //删除pos位置,注意删除的时候不能把头节点也删了,所以要做pos检查{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_sz--;return next;   //库里规定返回删除节点的下一个节点}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 clear()  //清楚除了头节点以外的所有节点{iterator it = begin();while (it != end()){it=erase(it);}_sz = 0;}~list()  //析构函数{clear();delete _head;_head = nullptr;}list(const list<T>& lt)   //拷贝构造{empty_init();for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_sz, lt._sz);}list<T>& operator=(list<T> lt)  //赋值重载{swap(lt);return *this;}private:Node* _head;  //头节点size_t _sz;   //记录链表的长度};}

🐬🤖本篇文章到此就结束了, 若有错误或是建议的话,欢迎小伙伴们指出;🕊️👻

😄😆希望小伙伴们能支持支持博主啊,你们的支持对我很重要哦;🥰🤩

😍😁谢谢你的阅读。😸😼

 

 

 

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

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

相关文章

【Vue】给 elementUI 中的 this.$confirm、this.$alert、 this.$prompt添加按钮的加载效果

文章目录 主要使用 beforeClose 方法实现 loading 的效果beforeClose MessageBox 关闭前的回调&#xff0c;会暂停实例的关闭 function(action, instance, done)1. action 的值为confirm, cancel或close。 2. instance 为 MessageBox 实例&#xff0c;可以通过它访问实例上的属…

Django + Bootstrap - 【echart】 统计图表进阶使用-统计用户日活日增、月活月增等数据(二)

一. 前言 Bootstrap是一个流行的前端框架&#xff0c;而ECharts是一个流行的可视化库。 Bootstrap可以用来设计网站和应用程序的用户界面&#xff0c;而ECharts可以用来创建交互式和可视化的图表。 chart.js中文文档&#xff1a;http://www.bootcss.com/p/chart.js/docs/ 二. …

LT8619B 是一款HDMI转TTL或者2 PORT LVDS的芯片。

LT8619B 1. 概述 LT8619B是龙迅基于清除边缘技术的高性能HDMI接收芯片&#xff0c;符合HDMI 1.4&#xff08;高清多媒体接口&#xff09;规范。RGB 输出端口可支持 RGB888/RGB666/RGB565 格式&#xff0c;输出分辨率最高可支持 4Kx2K 分辨率。凭借可编程标量&#xff0c;LT86…

切换.net Framework 版本后,出现NuGet 包是使用不同于当前目标框架的目标框架安装的,可能需要重新安装

问题现象&#xff1a; 由于添加新的dll文件&#xff0c;依赖的.NET Framework版本与当前的不一致&#xff0c;在vs 中切换了目标框架版本后&#xff0c;运行程序&#xff0c;出现以下的warnning信息&#xff1a; 一些 NuGet 包是使用不同于当前目标框架的目标框架安装的&#…

控制对文件访问

控制对文件访问 Linux文件权限 权限文件影响目录影响r读取文件内容列出目录内容w更改文件内容创建删除目录文件x作为命令执行目录可以变成当前工作目录 命令行管理文件系统权限 更改文件和目录权限 chmod chmod WhoWhatWhich file|directoryWho (u,g,o,a代表用户&#xff…

分布式事务 Seata

分布式事务 Seata 事务介绍分布式理论Seata 介绍Seata 部署与集成Seata TC Server 部署微服务集成 Seata XA 模式AT 模式AT 模式执行过程读写隔离写隔离读隔离 实现 AT 模式 TCC 模式TCC 模式介绍实现 TCC 模式 Saga 模式Seata 四种模式对比 事务介绍 事务&#xff08;Transac…

使用GPU进行大规模并行仿真,解决强化学习采样瓶颈:CPU、GPU架构以及原理详解

强化学习的落地应用场景,我认为可以是仿真环境仿真程度高,且仿真速度快的任务场景。而这篇帖子将会将:使用 GPU 进行大规模并行仿真,解决强化学习采样瓶颈。并直接举出三个例子,展示如何对原有的仿真环境进行修改,让它们适应 GPU 并行加速。 1.强化学习论文背后的仿真环…

降级npm后,出现xxx 不是内部或外部命令解决方法

比如我安装了anyproxy npm install anyproxy -g 之后在cmd中输入anyproxy 发现 anyproxy 不是内部或外部命令解决方法. 一般出现这样的问题原因是npm安装出现了问题&#xff0c;全局模块目录没有被添加到系统环境变量。 Windows用户检查下npm的目录是否加入了系统变量P…

【JavaEE】Tomcat的安装和使用、创建Mevan项目使用Servlet写一个程序

目录 前言 一、Tomcat的下载和安装 二、写一个简单的Servlet项目 1、创建一个Maven项目 2、引入依赖 3、创建目录 4、编写Servlet代码。 5、打包程序 6、将程序部署到Tomcat上 7、验证程序运行结果 三、在IDEA上安装Smart Tomcat插件 四、Servlet中的一些常见错误 …

计算机网络 day9 DNAT实验

目录 DNAT DNAT策略的典型应用环境 DNAT策略的原理 在网关中使用DNAT策略发布内网服务器 DNAT实验&#xff1a; 实验环境&#xff1a; DNAT网络规划拓扑图&#xff1a; 步骤&#xff1a; 1、创建linux客户端Web网站&#xff08;go语言&#xff09;&#xff0c;实现Web服…

RS485远传电表有哪些功能?

RS485远传电表是一种具有远程传输功能的电表&#xff0c;可以通过RS485接口进行数据传输。它主要用于远程测量电能消耗、监测电力质量和实时控制电力负载等方面。 RS485远传电表具有多种功能&#xff0c;如&#xff1a; 1.远程测量电能消耗&#xff1a;RS485远传电表可以通过远…

Fiddler网络调试器,抓包工具供大家学习研究参考

Fiddler 是一个 http 协议调试代{过}{滤}理工具&#xff0c;它能够记录并检查所有你的电脑和互联网之间的 http 通讯&#xff0c;设置断 点&#xff0c;查看所有的“进出”Fiddler 的数据(指 cookiehtmljscss等文件)。 Fiddler 要比其他的网络调试器要更加简单&#xff0c;因为…