【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.hqwc.cn/news/644050.html

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

相关文章

面包屑新玩法,ReactRouter+Ant Design实现动态渲染

在Ant Design中,可以通过Breadcrumb组件结合react-router库实现动态生成面包屑导航。具体步骤如下: 定义路由配置数据结构 我们需要在路由配置中添加额外的面包屑相关信息,例如面包屑标题、icon等。例如: const routes [{path: /,breadcrumbName: 首页},{path: /users,brea…

《S32G3系列芯片——Boot详解》持续更新中...

总目录&#xff1a;《S32G3系列芯片——Boot详解》持续更新中... ... 一、前言二、启动时序概述&#xff08;Boot Sequence&#xff09;三、启动特性&#xff08;Boot Features&#xff09;四、启动模式&#xff08;Boot Mode&#xff09;五、《S32G3系列芯片——Boot详解》系列…

BM25检索算法 python

1.简介 BM25&#xff08;Best Matching 25&#xff09;是一种经典的信息检索算法&#xff0c;是基于 TF-IDF算法的改进版本&#xff0c;旨在解决、TF-IDF算法的一些不足之处。其被广泛应用于信息检索领域的排名函数&#xff0c;用于估计文档D与用户查询Q之间的相关性。它是一种…

量子密钥分发系统的设计与实现(四):量子密钥的产生过程分析

在之前的文章中&#xff0c;我们讨论了QKD系统的光路系统&#xff0c;我们对整个系统最基础的部分有了初步的了解&#xff0c;从本文开始&#xff0c;我们就要往上层出发了&#xff0c;一起探讨下光电信号如何变成最终的密钥。 1.关于QKD后处理 在光路子系统中&#xff0c;Alic…

万界星空科技电机行业MES+商业电机行业开源MES+项目合作

要得出mes系统解决方案在机电行业的应用范围&#xff0c;我们先来看一下传统机电行业的管理难题&#xff1a; 1、 产品标准化程度较低&#xff0c;制造工艺复杂&#xff0c;生产周期较长&#xff0c;产品质量不稳定&#xff1b; 2、 自动化程度低&#xff0c;大多数工序以手工…

医学访问学者专栏—研究领域及工作内容

在国外访问学者申请中&#xff0c;医学领域的研究、教学及从业人员占有相当大的比例&#xff0c;这些医学访问学者的研究领域及工作内容都有哪些&#xff1f;本文知识人网小编就相关问题进行详细阐述&#xff0c;并附带案例说明。 一、在国外做医学访问学者可以从事哪些工作&am…

1142 - SELECT command denied to user ···

MySql子账户操作数据库权限不够&#xff0c;提示错误 1142 - SELECT command denied to user database 1142 - ALTER command denied to user database 以下命令可以解决 GRANT SELEC your_database_name TO mysql_account%;

Esp8266 - USB开关分享(开源)

文章目录 简介推广自己gitee项目地址:嘉立创项目地址&#xff1a;联系我们 功能演示视频原理图嘉立创PCB开源地址原理图PCB预览 固件烧录代码编译烧录1. 软件和驱动安装2. 代码编译1. 安装所需要的依赖库文件2. 下载源代码3. 烧录代码 使用说明1. 设备配网2. 打开设备操作页面3…

【kettle002】kettle访问人大金仓KingbaseES数据库并处理数据至execl文件

一直以来想写下基于kettle的系列文章&#xff0c;作为较火的数据ETL工具&#xff0c;也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 熟悉、梳理、总结下人大金仓KingbaseES数据库相关知识体系 kettle访问人大金仓KingbaseES数据库…

关于Spring事务管理之默认事务间调用问题

由事务的传播行为我们知道, 如果将方法配置为默认事务REQUIRED在执行过程中Spring会为其新启事务REQUIRES_NEW, 作为一个独立事务来执行. 由此存在一个问题。 如果使用不慎, 会引发org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back bec…

用于割草机器人,商用服务型机器人的陀螺仪

介绍一款EPSON推出适用于割草机器人&#xff0c;商用服务型机器人的高精度陀螺仪模组GGPM61&#xff0c;具体型号为GGPM61-C01。模组GGPM61是一款基于QMEMS传感器的低成本航向角输出的传感器模组&#xff0c;它可以输出加速度、角速度及姿态角等信息&#xff0c;为控制机器人运…

Git 保姆级教程(一):Git 基础

一、获取 Git 仓库 通常有两种获取 Git 项目仓库的方式&#xff1a; 1. 将尚未进行版本控制的本地目录转换为 Git 仓库&#xff1b; 2. 从其它服务器克隆 一个已存在的 Git 仓库。 两种方式都会在你的本地机器上得到一个工作就绪的 Git 仓库。 1.1 git init&#xff08;本地…