【STL】之 list 使用方法和模拟实现

news2024/11/15 17:48:11

目录

前言:

list是什么?

节点类

迭代器类:

list类

list的迭代器失效问题


前言:

之前我们分别手撕了string类和vector类,今天我们来跟list类打打交道~

list是什么?

通过查c++文档可知,list也是一个模板类,我们主要利用他进行数据的插入和删除操作,并且与vector不同的是,list的插入和删除操作用到的时间复杂度是O(1),而对于vector类的头插或者头删需要O(N)的时间复杂度,接下来让我们探索list是如何实现以及应用的吧!

通过查询文档可知,list的底层使用带头双向循环列表进行实现的,这样才能做到在任意位置删除和插入的时间复杂度都是O(1)。

节点类

因为是链表,我们需要先定义一个节点类,用来存储节点的相关信息。

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

注意这里的节点类以及下面的迭代器类都是用struct实现的,因为struct类默认成员都是public类型,方便我们在list类中进行操作,不然还要用到友元函数,减少不必要的麻烦。


迭代器类:

下面我们来实现list的迭代器类

我们之前实现的string和vector的迭代器都是原生指针,直接typedef指针即可,因为前者的底层存储空间是连续的,这样我们在使用迭代器进行遍历时,可以直接用指针++即可。

但是list类不同,list底层实现是用一个一个节点组成,是我们自定义类型实现,没有办法保证地址连续,因此迭代器直接++就无用武之地了。

因此我们要将Node*进行运算符重载,但是Node*本身是一个指针,只有自定义类型才能用运算符重载,因此我们需要一个类将Node*封装起来,然后对Node*进行运算符的重载~

//List的迭代器类
    template<class T, class Ref, class Ptr>
    struct ListIterator
    {
        typedef ListNode<T>* PNode;
        typedef ListIterator<T, Ref, Ptr> Self;
    
        PNode _pNode;

        ListIterator(PNode pNode = nullptr)
            :_pNode(pNode)
        {
           //_pNode = pNode;
        }
        //ListIterator(const Self& l)
        //{
        //    _pNode = l;
        //}
        T& operator*()
        {
            return _pNode->_val;
        }
        //T* operator->()
        //{
        //    return &(_pNode->_val);
        //}
        Self& operator++()
        {
            _pNode = _pNode->_pNext;
            return *this;
        }
        Self operator++(int)
        {
            Self tmp(*this);
            _pNode = _pNode->_pNext;
            return tmp;
        }
        Self& operator--()
        {
            _pNode = _pNode->_pPre;
            return *this;
        }
        Self& operator--(int)
        {
            Self tmp(*this);
            _pNode = _pNode->_pPre;
            return tmp;
        }
        bool operator!=(const Self& l)
        {
            return l._pNode != _pNode;
        }
        bool operator==(const Self& l)
        {
            return l._pNode == _pNode;
        }
    };

首先这里的迭代器存在一个很严重的问题:如果是一个const对象无法调用这个迭代器!而你可能会想说在创造一个const版本的迭代器类,这样固然是可以,但是这样会使得代码冗余!

public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T*> const_iterator;

这里就需要模板出手了!STL里面利用了三个模板参数,T, Ref,Ptr。而Ref,Ptr,分别就是引用和指针。所以当我们传递的是非const的迭代器,编译器就会匹配非const的,反之const就会匹配const。这就是大佬设计的独到之处~

const_iterator自己可以修改,不是const对象,但是指向的内容不能修改


list类

在实现了迭代器之后,我们就可以正式手撕list类了。 

    template<class T>
    class list
    {
        typedef ListNode<T> Node;
        typedef Node* PNode;
    public:
        typedef ListIterator<T, T&, T*> iterator;
        typedef ListIterator<T, const T&, const T*> const_iterator;
        ///
        // List的构造
        list()
        {
            CreateHead();
        }
        list(int n, const T& value = T());
        template <class Iterator>
        list(Iterator first, Iterator last);
        
        //拷贝构造
        list(const list<T>& l)
        {
            CreateHead();
            for (const auto i : l)
            {
                push_back(i);
            }
        }

        //赋值重载
        list<T>& operator=(const list<T> l)
        {
            swap(l);
            return *this;
        }


        ~list()
        {
            clear();
            delete _pHead;
            //注意delete之后,要将指针赋值为空指针
            _pHead = nullptr;
        }


        ///
        // List Iterator
        iterator begin()
        {
            return _pHead->_pNext;
        }
        iterator end()
        {
            return _pHead;
        }
        const_iterator begin()const
        {
            return _pHead->_pNext;
        }
        const_iterator end()const
        {
            return _pHead;
        }


        ///
        // List Capacity
        size_t size()const
        {
            size_t num = 0;
            const_iterator it = begin();
            while (it != end())
            {
                it++;
                num++;
            }
            return num;
        }

        //判断是否为空
        bool empty()const
        {
            return size() == 0;
        }

        
        // List Access
        T& front()
        {
            return _pHead->_pNext->_val;
        }
        const T& front()const
        {
            return _pHead->_pNext->_val;
        }
        T& back()
        {
            return _pHead->_pPre->_val;
        }
        const T& back()const
        {
            return _pHead->_pPre->_val;
        }


        
        // List Modify
        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)
        {
            PNode newnode = new Node(val);
            PNode prev = pos._pNode->_pPre;
            PNode cur = pos._pNode;

            prev->_pNext = newnode;
            newnode->_pPre = prev;
            newnode->_pNext = cur;
            cur->_pPre = newnode;

            //return iterator(newnode);
            //单参数的构造函数可以隐式类型转换
            return newnode;
        }


        // 删除pos位置的节点,返回该节点的下一个位置
        iterator erase(iterator pos)
        {
            //assert(pos != end());

            PNode cur = pos._pNode;
            PNode next = cur->_pNext;
            PNode prev = cur->_pPre;

            prev->_pNext = next;
            next->_pPre = prev;

            delete cur;
            cur = nullptr;

            return next;
        }

        void clear()
        {
            iterator it = begin();
            while (it != end())
            {
                it = erase(it);
            }
        }
        void swap(list<T>& l)
        {
            std::swap(l->_pHead, _pHead);
        }
    private:
        void CreateHead()
        {
            _pHead = new Node;
            _pHead->_pNext = _pHead;
            _pHead->_pPre = _pHead;
        }
        PNode _pHead;
    };

注意在实现的时候,可以多复用之前写过的代码,比如前插、前删、尾插、尾删就可以复用insert和erase函数。

拷贝构造函数也可以用push_back函数复用,析构函数使用erase复用~

list的迭代器失效问题

对于insert而言,因为insert方法仅仅只是改变了指针的指向,所以本质pos指向的那个节点的绝对地址并不会随着insert而改变,所以insert不会导致迭代器失效。

反而是erase方法反而因为释放了原来的空间导致出现野指针失效 而和vector的处理方式一致,erase方法也是返回指向被删除元素的下一个位置元素的迭代器。

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

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

相关文章

(2)基于巴法云+MQTT+微信小程序控制esp8266点灯

目录 1、wifi配置指令表 2、连接连接wifi网络 3、连接巴法云MQTT &#xff08;1&#xff09;配置用户属性ATMQTTUSERCFG ① 命令格式&#xff1a; ② 命令参数&#xff1a; ③ 实际配置方式&#xff1a; &#xff08;2&#xff09;配置ESP 设备连接的 MQTT broker ① 命令格式…

【AI大模型】自动辅助驾驶的“大模型”时代

&#x1f388;边走、边悟&#x1f388;迟早会好 一、自动辅助驾驶实现与设计 1. 系统架构 1.1. 传感器系统 摄像头&#xff1a;提供前视、侧视、后视等多角度图像数据&#xff0c;用于检测车道线、交通标志、行人和其他车辆。雷达&#xff08;RADAR&#xff09;&#xff1a…

【python】PyQt5中QRadioButton的详细用法教程与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

论文速递 | Operations Research 6月文章合集

编者按&#xff1a; 在本系列文章中&#xff0c;我们梳理了运筹学顶刊Operations Research在2024年6月份发布的9篇相关文章的基本信息&#xff0c;旨在帮助读者快速洞察领域新动态。 推荐文章1 题目&#xff1a;Tight Guarantees for Multiunit Prophet Inequalities and On…

list模拟实现--用节点封装的迭代器问题

目录 一、list的使用 1.1list的构造 1.2list的iterator ​编辑 1.3 list的capacity 1.4 list的element access ​编辑 1.5list的mdifiers ​编辑 二、list的迭代器失效问题 三、list的模拟实现 3.1定义一个节点类 3.2用节点去封装迭代器 编译器对->的优化问题 …

JavaScript做网页是否过期的处理

通过路由上的参数生成唯一md5和路由上token做验证_md5 token-CSDN博客 前言&#xff1a;基于这篇文章我们做网页是否超时&#xff0c;网页是否过期的处理。打开一个网页允许他在一定时间内可以访问&#xff0c;过了这个时间就不可以访问了&#xff0c;encrypt是h5加密方法&…

数据仓库基础理论—维度建模(图文详解)

数据仓库基础理论—维度建模 维度建模是数据仓库设计中的一种核心方法&#xff0c;旨在以业务角度组织数据&#xff0c;使其更易于理解、查询和分析。 1. 维度模型的基本概念 1.1 事实表&#xff08;Fact Table&#xff09;&#xff1a; 事实表是维度模型的核心&#xff0…

ARM编程指令二

一、算术指令 1. add指令 功能: 将两个操作数相加&#xff0c;并将结果存储在目标寄存器中。 ADD R0, R1, R2 // R0 R1 R22. sub指令 功能: 将第二个操作数从第一个操作数中减去&#xff0c;并将结果存储在目标寄存器中。 SUB R0, R1, R2 // R0 R1 - R23.ADC指令- 带…

强化学习机械臂

一.前言 这里记录一下我学习强化学习的一些知识&#xff0c;并希望在今后可以通过仿真成功验证算法&#xff0c;如果时间允许的情况下希望可以结合到真实机械臂上。 二.学习过程 机械臂强化学习实战&#xff08;stable baselines3panda-gym&#xff09; 这里我先用anaconda创…

【原创教程】电气电工常用剥线钳和压线钳(入门篇)

今天我们来看一下电气电工经常会用到的工具&#xff0c;剥线钳和压线钳。 首先我们看剥线钳做什么用&#xff1f;主要就是剥线&#xff0c;让内部的铜丝裸露。我们来看一下&#xff0c;我们经常用到的剥线钳。 1、带刃口剥线钳 2、自动剥线钳 3、鸭嘴剥线钳 下面看压嘴剥线钳…

Nodejs的使用

1.安装nodejs服务器。 java 项目可以运行在 tomcat 服务器&#xff0c;开始完成前后端完全分离。前端有自己独立的工程。我们需 要把前端独立的工程运行起来。 --- 运行在 nodejs 服务器下。 理解为 tomcat 服务器 安装成功后在命令窗口查看 1.1 安装npm java 项目需要依赖…

finalshell连接kali-Linux失败问题略谈

如果你正在使用fianlshell或者xshell等终端软件远程连接Linux进行工作&#xff0c;但是突然有一天&#xff0c;你死活连不上了&#xff0c;报错提示如下&#xff1a; java.net.ConnectException: Connection refused: connect 就像这样&#xff1a; 哪怕是重装虚拟机&#xff0…

HardSignin _ 入土为安的第十二天

有壳 55 50 58 用010 把vmp改成upx ctrlf2,查找main函数 点第三个 Ctrlx交叉引用 把花指令改了90 一共三处 找db按c 找函数按p封装&#xff0c;按f5反编译函数 smc 用pythonida绕一下 from ida_bytes import * addr 0x00401890 for i in range(170):patch_byte(addr i,…

排序算法----冒泡,插入,希尔,选择排序

冒泡排序 原理 冒泡排序实际上是交换排序&#xff0c;将大的数据通过交换的方式排到一边&#xff0c;依次进行 代码实现 void Swap(int* p1, int* p2) {int temp *p1;*p1 *p2;*p2 temp; }void BullerSort(int* a, int n) {for (int end n - 1; end > 0; end--){for …

卷积神经网络理论(CNN)·基于tensorflow实现

传统神经网络的输入是一维的数据(比如28*28的图片&#xff0c;需要转化为一维向量)。 而卷积神经网络的输入是一个三维的(比如RGB)。 结构 卷积神经网络有以下结构&#xff1a; 输入层卷积层池化层全连接层 输入层 顾名思义&#xff0c;输入层就是输入数据(可以是图片等数…

仅缺一位作者,年内书号

《工程测量学概论》缺第三 《风景园林设计与施工技术研究》缺第二 《对外汉语教学方法与实践研究》缺第三 《基于视觉传达设计下的民间艺术发展研究》缺第三 《英语教学基础与翻译技巧》缺第三 《博物馆学体系与博物馆探究学习》缺第三 《新时期高校辅导员工作与队伍建设研究》…

迈向数智金融:机器学习金融科技新纪元的新风采

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

怎么通过 ssh 访问远程设备

文章目录 什么是 SSH背景环境配置前置准备在 linux 系统中安装 ssh 组件 什么是 SSH ssh 全称是 Secure Shell, 有时候也被叫做 Secure Socket Shell, 这个协议使你能通过命令行的方式安全的连接到远端计算机。当连接建立就会启动一个 shell 会话&#xff0c;这时你就能在你的…

Kubernetes中间件监控指标解读

监控易是一款功能强大的IT监控软件&#xff0c;能够实时监控和分析各种IT资源和应用的状态&#xff0c;为企业提供全面而深入的监控服务。在Kubernetes中间件监控方面&#xff0c;监控易提供了详尽的监控指标&#xff0c;帮助用户全面了解Kubernetes集群的运行状态和性能表现。…

一键PDF翻译成中文,划重点轻松get

现在信息多得跟海一样&#xff0c;PDF文件里全是宝贵的资料和文章。但是&#xff0c;看着满屏幕的外国字&#xff0c;你是不是也头疼过&#xff1f;别发愁&#xff0c;今天咱们就来好好聊聊pdf翻译成中文的工具&#xff0c;帮你轻松搞定语言障碍&#xff0c;一点按钮&#xff0…