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

news2024/12/29 9:37:01

目录

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.coloradmin.cn/o/1619363.html

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

相关文章

Laravel 6 - 第十一章 中间件

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

Unity 如何制作和发布你的 Package

一、制作你的第一个 Package Unity Package 不做过多赘述&#xff0c;像 URP 本质上也是一个 Package&#xff0c;在 Unity 中可以通过菜单栏 → Window → Package manager 来管理你当前的所有 Package 本篇文章主要介绍&#xff1a;如何制作并发布属于你的 Package 1.1 Pac…

将本地项目推送至gitlab仓库

1. gitlab上新建一个空白项目 gitlab上点击new project按钮&#xff0c;新建一个项目 新建空白项目 项目名称与本地新建项目名称相同&#xff0c;其余根据具体需要选择 2. 初始化本地仓库并commit项目 进入本地项目根目录下&#xff0c;右击 git bash here打开命令窗口 初始化…

8.4.2 实验2:配置Trunk

1、实验目的 通过本实验可以掌握&#xff1a; Native VLAN 的含义和配置。IEEE802.1q 封装Trunk 配置和调试方法。 2、实验拓扑 配置 Trunk 的实验拓扑如下图所示。 图8-6 配置 Trunk 的实验拓扑 3、实验步骤 3.1 在交换机S1、S2上创建 VLAN 并把端口划分到相应的VLAN中 …

ASP.NET教务管理平台-权限及公共模块设计与开发

摘 要 随着教育改革的不断深化&#xff0c;高等院校的建设与发展对国民整体素质的提高起着越来越重要的作用&#xff0c;建立一套能够适应这些改变的行政管理方案也就显得尤为重要。对于教务处来说&#xff0c;将信息技术用于校务管理中便是迫切的要求。 教务系统中的用户…

ROS 2边学边练(33)-- 写一个静态广播(C++)

前言 通过这一篇我们将了解并学习到如何广播静态坐标变换到tf2&#xff08;由tf2来转换这些坐标系&#xff09;。 发布静态变换对于定义机器人底座与其传感器或非移动部件之间的关系非常有用。例如&#xff0c;在以激光扫描仪中心的坐标系中推理激光扫描测量数据是最简单的。 这…

Ventus(承影):基于RISC V的开源GPGPU

Ventus&#xff08;承影&#xff09;&#xff1a;基于RVV的开源GPGPU 清华大学集成电路学院dsp-lab的承影RVV GPGPU设计文档。 整体目标 提供一个开源的基于RVV的GPGPU实现方案&#xff0c;并给出软件映射方案、指令集&#xff08;支持的指令及特性、添加的自定义指令&#xf…

15.接口自动化学习-Mock(挡板/测试桩)

场景&#xff1a; 新需求还未开发时&#xff0c;使用mock提早介入测试&#xff0c;等后边开发后&#xff0c;进行调试 三方接口返回效率低&#xff0c;使用mock技术走通流程 1.mock方式 &#xff08;1&#xff09;如果会写django或flask,可以写简单对应的代码 &#xff08;…

小米一面:说说MVC与设计模式的关系

前言 大家好&#xff0c;我叫阿杆&#xff0c;不叫阿轩。 先来看看面试环节吧。 面试官&#xff1a;请说说MVC模式是基于哪种设计模式的&#xff1f; 求职者&#xff1a;MVC本身不就是一种设计模式吗&#xff1f; 面试官&#xff1a;我的意思是&#xff0c;MVC是基于23中设计…

【04-提升模型性能:集成学习与超参数优化】

文章目录 前言集成学习BaggingBoosting超参数优化随机搜索贝叶斯优化总结前言 在前几篇博文中,我们已经介绍了一些机器学习的基础知识、Scikit-learn的核心工具与技巧,以及如何评估模型性能。本篇博文将重点讲解两个可以显著提升机器学习模型性能的高级策略:集成学习和超参数…

python企业员工培训考试系统django+vue

这次开发的供电公司安全生产考试系统管理员&#xff0c;教师&#xff0c;学生。管理员功能有个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;主观题信息管理&#xff0c;主观题回答管理&#xff0c;主观题评分管理&#xff0c;成绩信息管理&#xff0c;试卷管理…

命名空间:namespace

对于无名命名空间 &#xff1a;但是不能再次定义相同名称的变量 在同一文件中

Linux程序调试优化(2)—— 一次学会嵌入式Linux下程序崩溃定位

文章目录 1.gdb调试1.1 gcc 编译时不带-g1.2 gcc 编译时带-g 2.coredump栈回溯2.1 使能core文件生成2.1 借助core文件回溯堆栈 3.backtrace捕捉SIGSEGV信号4.其他工具5.总结 写程序时&#xff0c;难免碰到程序崩溃的现象&#xff0c;而如何排查这些内存崩溃问题&#xff0c;例如…

Pulsar Meetup 深圳 2024 会务介绍

“ Hi&#xff0c;各位热爱 Pulsar 的小伙伴们&#xff0c;Pulsar Meetup 深圳 2024 报名倒计时啦&#xff0c;快来报名。这里汇集了腾讯、华为和谙流科技等大量 Pulsar 大咖&#xff0c;干货多多&#xff0c;礼品多多&#xff0c;不容错过啊。 ” 活动介绍 图片 由 AscentStre…

巧用波卡生态优势,Mythical Games 引领 Web3 游戏新航向

Polkadot 对创新、安全和治理的承诺为 Mythical Games 提供了极大的发展价值。这个链上生态不仅将支持 Mythical Games 成长发展&#xff0c;还将帮助其他 Mythos 合作伙伴来壮大建设项目。 —— Mythical Games 创始人兼首席执行官 John Linden 近期 Web3 游戏行业又有新动向&…

【管理咨询宝藏86】MBB物流公司营销项目价值定位与目标客户规划报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏86】MBB物流公司营销项目价值定位与目标客户规划报告 【格式】PDF版本 【关键词】战略规划、MBB、麦肯锡 【核心观点】 - 价值定位是企业希望深…

HORROR SYSTEM

HORROR SYSTEM是一个创新的工具包,允许开发者在Unity3D中创建独特的原创恐怖游戏。 HORROR SYSTEM是一款强大而灵活的工具,旨在基于Unity3D引擎创建沉浸式第三人称恐怖游戏。 这项资产易于使用且直观,可以让任何经验水平的开发人员将他们的想法付诸实践,创造出高质量、充满…

飞行汽车飞行控制系统功能详解

飞行汽车是一种创新的交通工具&#xff0c;结合了汽车和飞机的特点。它可以在陆地上行驶&#xff0c;同时也具备在空中飞行的能力。飞行汽车的概念已经存在多年&#xff0c;并且近年来随着技术的进步和研发的深入&#xff0c;这种交通工具正在逐渐从概念走向现实。 飞行汽车的…

NLP方面知识

NLP方面知识 一 基础1.Tokenizer1.1 分词粒度&#xff1a;1.2 大模型的分词粒度1.3 各路语言模型中的tokenizer 2.Embedding layer2.1 理解Embedding矩阵 一 基础 1.Tokenizer tokenizer总体上做三件事情&#xff1a; 分词。tokenizer将字符串分为一些sub-word token string&…

学习Rust的第10天:枚举和模式匹配

今天我们来看看一个类似的概念 enums 。 Enums: We saw that in Rust, enums are data types that list possible values, giving a simple and type-safe mechanism to describe alternatives. We looked at how to create enums and use them to represent similar possibili…