【C++】手撕list(list的模拟实现)

目录

01.节点

02.迭代器

迭代器运算符重载

03.list类

(1)构造与析构

(2)迭代器相关

(3)容量相关

(4)访问操作

(5)插入删除


我们在学习数据结构的时候,学过一个非常好用的结构,叫做带头双向循环链表,它不仅遍历非常地灵活方便,而且插入和删除操作的效率很高,弥补了单链表相较于数组的缺点。我们今天要讲的list模版底层就是带头双向循环链表。

01.节点

既然是链表,链表是由一个个节点组成的,每一个节点都需要独立开辟空间,节点之间通过指针进行连接,而双向循环链表的节点内部包含两个指针,一个指向下一个节点,一个指向上一个节点。

    // List的节点类template<class T>struct ListNode{ListNode(const T& val = T()): _prev(nullptr), _next(bullptr), _val(val){}ListNode<T>* _prev;ListNode<T>* _next;T _val;};

这里用struct定义类是因为,struct定义的类内部成员默认为公有,而class定义的类内部成员默认私有,节点的参数需要支持外部访问,所以这里用struct定义。

02.迭代器

在vector中,迭代器通常是一个原生指针,因为vector内部是使用连续的内存块来存储数据的,所以可以直接用指针来表示迭代器。

但是list使用双向链表存储数据,其迭代器就不能只是指针了,而需要存储更多的数据,并且迭代器的加减等运算操作也需要重载

迭代器运算符重载

1.*解引用

使用 *iter 访问迭代器时,operator*() 返回的是 _node->_val 的引用,可以直接对返回值进行操作,就好像它是一个对象一样。

2.->成员访问

使用迭代器的 -> 操作符时,实际上是先调用 operator->() 返回 _node->_val 的地址,然后对返回的地址进行成员访问。

3.迭代器++、--

分为前置++与后置++:

前置++:

  • 调用前置++时,先将 _node 指向下一个节点。
  • 返回的是递增后的对象的引用
  • 返回的引用允许对递增后的对象进行连续操作

后置++:

  • 调用后置++时,先创建一个当前对象的副本 temp
  • _node 指向下一个节点。
  • 返回的是之前的对象的副本 temp
  • 返回的副本 temp 保留了递增前的状态,允许在返回后继续使用递增前的对象

--与++同理,只不过--是指向前一个节点,将“ _node = _node->_next”改成“ _node = _node->_prev”即可。

4.==运算符

判断两个迭代器是否相等就是判断他们的节点是否相等。

//List的迭代器类template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;public:ListIterator(Node* node = nullptr): _node(node){}ListIterator(const Self& l): *this(l){}T& operator*(){return _node->_val;}T* operator->(){return &(operator*());}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self temp(*this);_node = _node->_next;return temp;}Self& operator--(){_node = _node->_prev;return *this;}Self& operator--(int){Self temp(*this);_node = _node->_prev;return temp;}bool operator!=(const Self& l){return _node != l._node;}bool operator==(const Self& l){return _node == l._node;}private:Node* _node;};

03.list类

(1)构造与析构

在创建一个list实例时,首先需要创建一个头节点,不存储任何有效数据。用于简化链表的操作,以及提供一种统一的操作方式。

    private:void CreateHead(){_head = new node;_head->_prev = _head;_head->_next = _head;}Node* _head;};

list的构造分为无参构造、实例化构造、迭代器构造、拷贝构造等等。

无参构造:

只需要创建一个头节点,CreateHead()已经完成了初始化,就不需要默认构造了

        list(){CreateHead();}

 实例化构造:

实例化n个value元素,在head节点后面插入n个value元素。

        list(int n, const T& value = T()){CreateHead();for (int i = 0; i < n; ++i) {push_back(value);}}

 迭代器构造:

利用迭代器对拷贝源进行遍历,在head节点后面不断插入数据。

        template <class Iterator>list(Iterator first, Iterator last){CreateHead();while (first != last) {push_back(*first);++first;}}

拷贝构造:

复制一个拷贝源副本,然后赋值给目标容器。 

        list(const list<T>& l){CreateHead();//_head = l->_head;//浅拷贝list temp<T>(l.begin(), l.end());this->swap(temp);}

重载=:

创建赋值源的副本并作为返回值返回。 

        list<T>& operator=(const list<T> l){list temp<T>(l.begin(), l.end());return *temp;}

析构:

由于list内部数据存储地址是分散的,析构时要对每一个节点单独进行空间释放,这里我们可以用头删的方法,头节点保留,依次删除头节点指向的首元素节点,并改变指向,这样就可以遍历容器达到析构效果。

        void clear(){Node cur = _head;while (cur != _head){_head->_next = cur->_next;delete cur;cur = _head->_next;}_head->_next = _head->_prev = _head;}~list(){clear();delete _head;_head = nullptr;}

(2)迭代器相关

我们需要定义定义 begin()end() 函数,用于返回指向链表开头和结尾的迭代器。

begin() 函数返回的迭代器指向链表的第一个有效节点,即头节点 _head 的下一个节点 _head->_next。因为头节点 _head 不存储有效数据,而是作为链表的辅助节点。

end() 函数返回的迭代器指向链表的结尾,即头节点 _head。因为在list中,尾节点指向的下一个节点就是头节点 _head。

        // List 迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}

(3)容量相关

size()函数:

要想知道list容器中的数据个数,就需要遍历全部节点,因为是循环链表,遍历到_head头节点时即表示遍历完毕。

        size_t size()const{size_t count = 0;Node* cur = _head->_next;while (cur != _head){++count;cur = cur->_next;}return count;}

empty()函数:

容器为空的判定条件是:头结点的两个指针都指向自己,即为初始化状态。

        bool empty()const{return _head->_next == _head;}

(4)访问操作

front()back() 函数,分别用于获取链表的第一个元素和最后一个元素。这是直接获取元素的值,而 begin()end() 函数是获取地址。

        T& front(){return _head->_next->_val;}const T& front()const{return _head->_next->_val;}T& back(){return _head->_prev->_val;}const T& back()const{return _head->_prev->_val;}

注意:想要对const类型进行函数重载,函数后面必须加上const关键字,才能构成重载。

(5)插入删除

插入和删除分为头插、头删;尾插、尾删;在pos位置前插入和删除。

由于前两种可以看做是第三种的特殊情况,所以只需要具体实现第三种即可。

在pos位置前插入

首先需要创建一个新节点newNode,newNode 的 _prev 指向 rightNode 的 _prev ,newNode 的 _next 指向 leftNode 的 _next

 再将 rightNode 的 _prev 、leftNode 的 _next 都指向 newNode ,就完成了插入操作,插入完成后需要返回插入位置的迭代器。

        // 在pos位置前插入值为val的节点iterator insert(iterator pos, const T & val){Node* _pnewnode = new Node;Node* cur = pos._node;_pnewnode->_val = val;_pnewnode->_next = cur;_pnewnode->_prev = cur->_prev;cur->_prev = _pnewnode;return iterator(_pnewnode);}

删除pos位置的节点

首先将leftNode 的 _next 指向 rightNode ,rightNode 的 _prev 指向 leftNode。

 然后对pos位置节点进行空间释放。

        // 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){Node* pDel = pos._node;Node* pRet = pos._node->_next;pDel->_prev->_next = pDel->_next;pRet->_prev = pDel->_prev;delete pDel;return iterator(pRet);}

头插头删就是在首元素节点前插入节点;尾插尾删就是删除头结点_prev指向的节点,是上述插入删除操作的实例:

        // List 插入和删除void push_back(const T& val) { insert(end(), val);}void pop_back() { erase(--end());}void push_front(const T& val) {insert(begin(), val);}void pop_front(){erase(begin());}

最后附上完整代码:

#pragma once
#include<iostream>
using namespace std;namespace My
{// List的节点类template<class T>struct ListNode{ListNode(const T& val = T()): _prev(nullptr), _next(bullptr), _val(val){}ListNode<T>* _prev;ListNode<T>* _next;T _val;};//List的迭代器类template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;public:ListIterator(Node* node = nullptr): _node(node){}ListIterator(const Self& l): *this(l){}T& operator*(){return _node->_val;}T* operator->(){return &(operator*());}Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self temp(*this);_node = _node->_next;return temp;}Self& operator--(){_node = _node->_prev;return *this;}Self& operator--(int){Self temp(*this);_node = _node->_prev;return temp;}bool operator!=(const Self& l){return _node != l._node;}bool operator==(const Self& l){return _node == l._node;}private:Node* _node;};//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;public:///// List的构造list(){CreateHead();}list(int n, const T& value = T()){CreateHead();for (int i = 0; i < n; ++i) {push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();while (first != last) {push_back(*first);++first;}}list(const list<T>& l){CreateHead();//_head = l->_head;//浅拷贝list temp<T>(l.begin(), l.end());this->swap(temp);}list<T>& operator=(const list<T> l){list temp<T>(l.begin(), l.end());return *temp;}~list(){clear();delete _head;_head = nullptr;}///// List 迭代器iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}///// List 容量相关size_t size()const{size_t count = 0;Node* cur = _head->_next;while (cur != _head){++count;cur = cur->_next;}return count;}bool empty()const{return _head->_next == _head;}// List 元素访问操作T& front(){return _head->_next->_val;}const T& front()const{return _head->_next->_val;}T& back(){return _head->_prev->_val;}const T& back()const{return _head->_prev->_val;}// List 插入和删除void push_back(const T& val) { insert(end(), val);}void pop_back() { erase(--end());}void push_front(const T& val) {insert(begin(), val);}void pop_front(){erase(begin());}// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T & val){Node* _pnewnode = new Node;Node* cur = pos._node;_pnewnode->_val = val;_pnewnode->_next = cur;_pnewnode->_prev = cur->_prev;cur->_prev = _pnewnode;return iterator(_pnewnode);}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){Node* pDel = pos._node;Node* pRet = pos._node->_next;pDel->_prev->_next = pDel->_next;pRet->_prev = pDel->_prev;delete pDel;return iterator(pRet);}void clear(){Node cur = _head;while (cur != _head){_head->_next = cur->_next;delete cur;cur = _head->_next;}_head->_next = _head->_prev = _head;}void swap(list<T>& l){std::swap(_head, l._head);}private:void CreateHead(){_head = new node;_head->_prev = _head;_head->_next = _head;}Node* _head;};
};

那么以上就是list的模拟实现了,欢迎在评论区留言,觉得这篇博客对你有帮助的可以点赞关注收藏支持一波喔~😉

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

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

相关文章

使用 GitHub Actions 实现项目的持续集成(CI)

目录 什么是 GitHub Actions 基础概念 Workflow 文件 Workflow 语法 实例&#xff1a;编译 OpenWrt 什么是 GitHub Actions GitHub Actions 是 GitHub 推出的持续集成&#xff08;Continuous Integration&#xff0c;简称 CI&#xff09;服务它允许你创建自定义工作流&am…

黑马面试篇1(续)

黑马面试篇1-CSDN博客&#xff08;续集&#xff09; 六、消息中间件篇 6.1 RabbitMQ 1&#xff09;使用场景&#xff1a; 异步发送&#xff08;验证码、短信、邮件…&#xff09;MYSQL和Redis , ES之间的数据同步分布式事务削峰填谷… 2&#xff09;RabbitMQ消息的重复消费问…

分享三款可以给pdf做批注的软件

PDF文件不像Word一样可以直接编辑更改&#xff0c;想要在PDF文件上进行编辑批注需要用到一些专业的软件&#xff0c;我自己常用的有三款&#xff0c;全都是官方专业正版的软件&#xff0c;功能丰富强大&#xff0c;使用起来非常方便&#xff01; 1.edge浏览器 这个浏览器不仅可…

【Spring】Spring中AOP的简介和基本使用,SpringBoot使用AOP

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 一、AOP简介 AOP的全称是Aspect-Oriented Programming&#xff0c;即面向切面编程&#xff08;也称面向方面编程&#xff09;。它是面向对象编程&#xff08;OOP&#xff09;的一种补充&#xff0c;目前已成为一种比较成…

ton-http-api安装部署

1、拉取github代码 mkdir /data git clone https://github.com/toncenter/ton-http-api.git cd ton-http-api2、创建环境变量 ./configure.py cat .env TON_API_CACHE_ENABLED0 TON_API_CACHE_REDIS_ENDPOINTcache_redis TON_API_CACHE_REDIS_PORT6379 TON_API_CACHE_REDIS_T…

Facebook’s Tectonic Filesystem: Efficiency from Exascale——论文阅读

FAST 2021 Paper 分布式元数据论文阅读笔记整理 背景 Blob storage 用来存放大量的文本、图片、视频等非结构化数据 包含 EB 级别的数据 存储内容大小不一&#xff0c;大小几KB到几MB不等 要求低时延 使用 Haystack 和 F4 Data warehouse 存放用于数据分析和机器学习的…

Leetcode—1232. 缀点成线【简单】

2024每日刷题&#xff08;122&#xff09; Leetcode—1232. 缀点成线 算法思想 实现代码 class Solution { public:bool checkStraightLine(vector<vector<int>>& coordinates) {int x0 coordinates[0][0];int y0 coordinates[0][1];int x1 coordinates[1…

Excel 中用于在一个范围中查找特定的值,并返回同一行中指定列的值 顺序不一样 可以处理吗

一、需求 Excel 中&#xff0c;在一列&#xff08;某范围内&#xff09;查找另一列特定的值&#xff0c;并返回同一行中另一指定列的值&#xff0c; 查找列和返回列的顺序不一样 二、 实现 1、下面是一个使用 INDEX 和 MATCH 函数的例子&#xff1a; 假设你有以下数据&…

python数据可视化:雷达图

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 python数据可视化&#xff1a; 雷达图 选择题 关于以下代码输出的雷达图中&#xff0c;以下说法正确的是&#xff1f; import numpy as np import matplotlib.pyplot as plt from pylab impor…

看懂原理图

EL3H7光耦 作用&#xff1a; 光耦还可以隔离驱动电机什么的、485隔离通讯啊、pwm信号传输&#xff0c;韦根&#xff0c;强电压。 参考&#xff1a;光耦应用及参数设计_el3h7光耦中文资料-CSDN博客

C# winform 漂亮的日期时间控件

源代码下载&#xff1a; https://download.csdn.net/download/gaoxiang19820514/89242240 效果图 在 HZH-Controls控件 基础上修改的日期控件 因为HZH_Controls控件 中的日期控件太大了&#xff0c; 我的程序中需要多个日期时间的控件放不下&#xff0c;主题是绿色的&#…

【介绍下Selenium】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

SpringWebFlux RequestBody多出双引号问题——ProxyPin抓包揪出真凶

缘起 公司有个服务做埋点收集的&#xff0c;可以参考我之前的文章埋点日志最终解决方案&#xff0c;今天突然发现有些数据日志可以输出&#xff0c;但是没法入库。 多出的双引号 查看Flink日志发现了JSON解析失败&#xff0c;Flink是从Kafka拿数据&#xff0c;Kafka本身不处…

C++信息学奥赛 数据结构认识

数据结构 1.1数据结构分类 1.2基本数据类型 1.3数字编码 1.4字符编码 1.1数据结构分类 数据结构如同一副稳固而多样的框架。为数据的有序组织提供了蓝图&#xff0c;算法得以在此基础上生动起来。 常用的数据结构包括哪些 &#xff0c; &#xff0c; &…

摩根大通推出创新工具 FlowMind,引领金融自动化新变革

近日&#xff0c;摩根大通人工智能研究部推出了一款极具创新性的工具——FlowMind&#xff0c;为金融行业带来了全新的工作模式和效率提升。 FlowMind 能够自动化金融工作流程&#xff0c;在信贷审批、风险评估、合规监测等重要任务中发挥着关键作用。它利用 GPT 自动生成工作…

蓝网科技临床浏览系统 deleteStudy SQL注入漏洞复现(CVE-2024-4257)

0x01 产品简介 蓝网科技临床浏览系统是一个专门用于医疗行业的软件系统,主要用于医生、护士和其他医疗专业人员在临床工作中进行信息浏览、查询和管理。 0x02 漏洞概述 蓝网科技临床浏览系统 deleteStudy接口处SQL注入漏洞,未经身份验证的恶意攻击者利用 SQL 注入漏洞获取…

Zynq 7000 系列之启动模式—NOR启动

NOR Boot是一种启动模式&#xff0c;它指的是当芯片上电时&#xff0c;芯片从NOR Flash的起始位置开始取代码执行。在NOR Flash的开头处&#xff0c;通常存储着8个向量表&#xff0c;其中包含了用于引导系统的指令。这些指令中的b reset是一个相对跳转指令&#xff0c;意味着不…

windows11家庭版开启Hyper-v

前提&#xff1a;如果在控制面板中-->程序和功能-->启用和关闭windows功能-->没有Hyper-v 1.什么是Hyper-v&#xff1f; Hyper-v分为两个部分&#xff1a;底层的虚拟机平台、上层的虚拟机管理软件 2.Hyper-v安装 2.1新建hyper.cmd文件&#xff0c;写入下面的内容&…

第三节课,功能2:开发后端用户的管理接口--http client -- debug测试

一、idea 中 Http client 使用 二、测试步骤&#xff0c;先进入主程序 2.1 先run &#xff0c;再debug 2.2 再进入想要测试的代码 2.2.1 进入测试的接口 三、程序逻辑 1&#xff09;用户注册逻辑&#xff1a;如果用户不存在再后端&#xff0c;看用户名&密码&校验码是…

邦注科技 模具清洗机 干冰清洗机 干冰清洗设备原理介绍

干冰清洗机&#xff0c;这款神奇的清洁设备&#xff0c;以干冰颗粒——固态的二氧化碳&#xff0c;作为其独特的清洁介质。它的工作原理可谓独具匠心&#xff0c;利用高压空气将干冰颗粒推送至超音速的速度&#xff0c;犹如一颗颗银色的流星&#xff0c;疾速喷射至待清洗的物体…