当前位置: 首页 > news >正文

C++STL之list的模拟实现

目录

一.list准备

二. iterator迭代器

1._list_iterator

2.begin()、end()

3.const_begin()、const_end()

4.!=&&==

5.++ && --

6.operator*

7.operator->

三.Modify(修改)

1.insert()

2.erase()

3.push_back() && push_front()

4.pop_back&&pop_front

5.clear

四.constructor构造函数

迭代构造

拷贝构造

五.destructor析构函数


STL中list的使用也是和之前讲的类似,常用的接口就那些,可以参考list官方文档http://www.cplusplus.com/reference/list/list/?kw=list

本文章主要讲list的模拟实现.

list相当于是一个链表,对list操作相当于对链表操作

下面是对list的介绍

1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

一.list准备

上面提到了list是双向链表,而且是由每一个结点组成,所以我们先定义结点,包括三个成员:

_data数据,_prev指向前一个结点的指针,_next指向下一个结点的指针.

当然数据类型不能确定,使用模版来解决

	template<class T>class list_node{T _data;list_node<T>* _next;list_node<T>* _prev;};

结点创建完毕,就该创建链表了,链表只有一个类成员,既头结点,将上面结点名字重新定义

typedef list_node<T> Node;	
private:Node* _head;

一切准备就绪,开始正式工作

构造函数中初始化时,(list的构造函数)首先new一个头结点_head,然后因为是双向循环链表,而且一开始没有数据,所以让head的prev指向head,head的next指向head.

		list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}

当然,还有结点的构造函数,方便我们后续进行new新的结点.

		list_node(const T& x = T()):_data(x),_next(nullptr),_prev(nullptr){}

这些构造函数只是暂时需要用到的,并不完整,文章下文会补充完善的.

二. iterator迭代器

关于迭代器,上一章我说了vector的模拟实现它的内部是由原生指针实现的,它之所以可以用原生指针,主要是因为vector的空间是连续的.

而list的空间是不连续的,所以不能直接使用原生指针.

但我们可以实现一个迭代器,然后内部实现对应的各种运算符操作.例如“++”,“--”等.

然后可以直接把指针转成迭代器使用即可.

下面开始实现迭代器.

1._list_iterator

这个类就是迭代器,它看起来像一个"指针"一样,进行各种比较或各种操作,它指向的是原链表中的一个结点,所以每次必须将原链表中的一个结点传入来构造迭代器.

这样的话,它的成员类型就是结点的类型list_node.

template<class T>//(暂时)
struct _list_iterator{typedef list_node<T> Node;_list_iterator(Node* node):_node(node){}}

2.begin()、end()

这两个函数应该很常见了,返回首元素,和尾元素.

但是问题是双向链表的首尾元素分别在哪呢

双向链表一开始存在一个头结点,这个头结点不存储任何数据,它的next才是首元素,它的prev是最后一个元素.

begin()的位置是第一个元素,end()的位置是头结点的位置,如下图

 

所以begin()要返回头结点的next,end()直接返回头结点即可.

		iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}

3.const_begin()、const_end()

之前写vector的时候,对于const类型我们直接加上const即可.但是这里就不可以了

之前我们写了begin(),返回类型是iterator,但是这个是const_iterator begin()

它俩之间不能构成重载,因为函数重载规则中没有返回值不同可以构成函数重载这一说.

这样就有了两个同名的函数,但是不能构成重载,这是编译不过去的.那我们该怎么解决呢?

可以在函数后加一个const来区分,但是当执行到_head->next时,由于加了const,_head已经不可以被解引用,引发编译错误.

这样也不可以,那该怎么办呢》

根据STL源码,它的迭代器有三个模板参数,分别为T,Ref,Ptr

普通迭代器三个参数为:T,T&,T*

const迭代器也有三个参数分别为T,const T&,const T*

所以说:STL源码是通过实例化出的对象类型不同来区分是普通对象还是const对象的.

此时代码如下:

	template<class T,class Ref, class Ptr>typedef _list_iterator<T, T&, T*> iterator;typedef _list_const_iterator<T, const T&, const T*> const_iterator;const_iterator begin() const{return iterator(_head->_next);}const_iterator end() const{return iterator(_head);}

4.!=&&==

这个很简单,直接返回两个迭代器相等或不相等即可.

		bool operator!=(const iterator& it){return _node != it->_node;}bool operator==(const iterator& it){return _node == it->_node;}

5.++ && --

++

++有前置++也有后置++,实现方法具体可以看我之前讲的类和对象,仔细讲解了这些思路.

前置++,既先让node指向node的next即可,然后返回*this.

		iterator& operator++(){_node = _node->_next;return *this; }

后置++

为了区分前置++,需要在参数里面加一个int

思路是先保存当前结点,然后再让node指向node的next,最后返回tmp

		iterator& operator++(int){iterator tmp(*this);_node = _node->_next;return tmp;}

-- 

前置--

和上面的思路一样,只是把next改成prev

		iterator& operator--(){_node = _node->_prev;return *this;}

后置--

		iterator& operator--(int){iterator tmp(*this);_node = _node->_prev;return tmp;}

6.operator*

*就是解引用,就是返回这个结点对应的值,所以直接返回data即可.

Ref此时为T&类型,可以读也可以写.

		Ref operator*(){return _node->_data;}

7.operator->

->这个操作符一般用在结构体指针中,并且左操作数必须为指针类型,所以我们返回的类型也应该为指针类型,既Ptr(T*)

只不过是与*的返回类型不同

		Ptr operator->(){return& (operator*()); //&(_node->data)}

三.Modify(修改)

这都是双链表的一些修改,在我之前写的里面有详细的讲解.这里便不再仔细讲述了.

1.insert()

		iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;//创建新结点并连接Node* newnode = new Node(x);prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}

2.erase()

		iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}

3.push_back() && push_front()

这里直接复用了之前的insert代码

push_back()

		void push_back(const T& x){Node* tail = _head->_prev;Node* newnode = new Node;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}

push_front()

    void push_front(const T& val){insert(begin(), val);}

4.pop_back&&pop_front

pop_back()

    void pop_back(){erase(--end());}

pop_front()

    void pop_front(){erase(begin());}

5.clear

思路就是从头遍历链表,直到遇到end(),每遍历一个元素,就erase一个

    void clear(){iterator p = begin();while(p != end()){p = erase(p);}}

四.constructor构造函数

无参构造函数第一部分已经说了,接下来说迭代构造和拷贝构造.

由于list的初始化构造中一部分需要经常利用,我们将其封装并放入私有成员里.

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

迭代构造

这个类似于vector的迭代器构造,可以参考上一章

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

拷贝构造

    list(const list<T>& l){CreateHead();// 用l中的元素构造临时的temp,然后与当前对象交换list<T> temp(l.cbegin(), l.cend());this->swap(temp);}

这里的swap我们需要自己实现一下,交换两个链表的头结点即可.

       void swap(list<T>& lt){std::swap(_head, lt._head);}

 

五.destructor析构函数

这个我们可以复用之前的clear,然后最后手动释放掉头结点.

    ~list(){clear();delete _pHead;_pHead = nullptr;}

list的模拟实现就到此为止了.

有不懂或错误的地方欢迎提问或指正哦

相关文章:

C++STL之list的模拟实现

目录 一.list准备 二. iterator迭代器 1._list_iterator 2.begin()、end() 3.const_begin()、const_end() 4.!&& 5. && -- 6.operator* 7.operator-> 三.Modify(修改) 1.insert() 2.erase() 3.push_back() && push_front() 4.pop_bac…...

为什么硬件性能监控很重要

当今的混合网络环境平衡了分布式网络和现代技术的实施。但它们并不缺少一个核心组件&#xff1a;服务器。保持网络正常运行时间归结为监控和管理导致网络停机的因素。极有可能导致性能异常的此类因素之一是硬件。使用硬件监控器监控网络硬件已成为一项关键需求。 硬件监视器是…...

HTTP缓存

HTTP缓存HTTP缓存引发的一个问题HTTP缓存的作用HTTP缓存的分类强制缓存协商缓存&#xff08;解决强缓存下资源不更新问题&#xff09;缓存策略HTTP缓存引发的一个问题 有一次在开发移动端H5项目&#xff0c;UI提了几个UI问题&#xff0c;经过样式调试&#xff0c;android上没有…...

SPI设备树处理过程

SPI设备树处理过程 文章目录SPI设备树处理过程参考资料&#xff1a;一、 spi_device结构体二、 SPI设备树格式2.1 SPI Master2.2 SPI Device2.3 设备树示例三、设备树实例3.1 使用GPIO模拟的SPI控制器3.2 IMX6ULL SPI控制器四、 设备树处理过程致谢参考资料&#xff1a; 内核头…...

数据有哪些重要的作用?

我们正处在科技高速发展的时代&#xff0c;如今互联网已经与我们的生活息息相关&#xff0c;我们每天在互联网产生大量的数据&#xff0c;这些数据散落在网络中看似没有怎么作用&#xff0c;但是这些数据经过系统的处理整合起来确实非常有价值的。 一、 发展大数据技术可以提高…...

spring面试题总结

1、spring是什么&#xff1f; spring是一个轻量级IOC和AOP容器框架&#xff0c;是为Java应用程序提供基础性服务的一套框架&#xff0c;目的是用于简化企业应用的开发&#xff0c;开发者只需要关注业务需求即可&#xff1a; core container 容器组件 spring context&#xff0c…...

使用MUI与H5+构建移动端app

前言 通过mui构建APP 效果图: <!DOCTYPE html> <html> <head><meta charset...

第17篇:Java变量总结

目录 1.变量的概念 1.1 变量来源 1.2 计算机中的变量 1.3 变量如何在内存中存储 2.Java变量...

使用51单片机的GPIO输出占空比可调节的PWM波

一、前言 在一些单片机或微控制器中&#xff0c;通用GPIO可以被配置为产生PWM信号。PWM即脉冲宽度调制&#xff0c;是一种用于模拟输出的技术。它可以通过改变输出信号的脉冲宽度来控制电路中的电平&#xff0c;从而实现对电路的控制。 二、什么是PWM波&#xff1f; PWM波&a…...

从产品经理的角度如何提升项目的交付质量?

提高交付质量 &#xff0c;对于每个IT公司都是永恒的话题。 交付质量其实包含2重意义&#xff0c; 一是交付的高质量&#xff08;客户角度&#xff09;&#xff0c;即客户的满意度&#xff1b;二是高质量的交付&#xff08;交付团队的角度&#xff09;&#xff0c;这里是指如何…...

JavaScript BOM【快速掌握知识点】

目录 Window对象的常用属性 语法&#xff1a; Window对象的常用方法 语法&#xff1a; open()和close()方法 History对象 常用属性和方法 示例 Location对象 常用属性 常用方法 Document对象的常用方法 定时函数 超时调用&#xff1a;setTimeout() 间歇调用&…...

【算法】哈希表

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;或许会很慢&#xff0c;但是不可以停下来&#x1f43e; 文章目录1.定义2.优点3.数字哈希3.1拉链法3.2开放寻址法3.3 例题4.字符串哈希1.定义 哈希表&#xff08;Hash table&#xff09;&#xff0c;是根据键…...

彻底搞懂React-hook链表构建原理

写在前面的小结 每一个 hook 函数都有对应的 hook 对象保存状态信息useContext是唯一一个不需要添加到 hook 链表的 hook 函数只有 useEffect、useLayoutEffect 以及 useImperativeHandle 这三个 hook 具有副作用&#xff0c;在 render 阶段需要给函数组件 fiber 添加对应的副…...

【数据挖掘实战】——应用系统负载分析与容量预测(ARIMA模型)

项目地址&#xff1a;Datamining_project: 数据挖掘实战项目代码 目录 一、背景和挖掘目标 1、问题背景 2、传统方法的不足 2、原始数据 3、挖掘目标 二、分析方法与过程 1、初步分析 2、总体流程 第一步&#xff1a;数据抽取 第二步&#xff1a;探索分析 第三步&a…...

【华为OD机试模拟题】用 C++ 实现 - 九宫格按键输入(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明九宫格按键输入题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高…...

Linux: config: CONFIG_SYN_COOKIES

文章目录 CONFIG_SYN_COOKIESLinux kernel里的超时设置Huawei SBC详细工作机制CONFIG_SYN_COOKIES config SYN_COOKIES,布尔值;是否支持IP:TCP syncookie功能。 详解:一般来说TCP/IP网络不能够阻挡SYN flooding工具。这个工具很容易被利用,而且会导致DOS工具,妨碍其他整…...

【笔记】C# 数据类型转换

文章目录前言类型转换的概念1&#xff0c;隐式转换2&#xff0c;显式转换3&#xff0c;程序类转换结语前言 &#x1f33b; 大家好啊&#xff0c;我是writer桑&#xff0c;本章是关于 C# 数据类型转换的一个总结&#xff0c;其中包含隐式、显示转换和程序类转换&#xff0c;方便…...

JavaWeb JavaBean,MVC三层架构

9、JavaBean 实体类 JavaBean有特定的写法&#xff1a; 必须要有一个无参构造属性必须私有化必须有对应的get/set方法&#xff1b; 一般用来和数据库的字段做映射 ORM&#xff1b; ORM &#xff1a;对象关系映射 表—>类字段–>属性行记录---->对象 people表 …...

JavaEE简单实例——MyBatis一对多关联映射的嵌套结果集查询

简单介绍&#xff1a; 在之前的章节&#xff0c;我们简单介绍了MyBatis中的一对一的关联查询&#xff0c;使用了嵌套查询和嵌套结果集两种方式进行讲解&#xff0c;但是在实际的使用中&#xff0c;我们常用的是嵌套结果集的查询方式&#xff0c;所以在一对多的查询中&#xff…...

大数据框架之Hadoop:MapReduce(三)MapReduce框架原理——OutputFormat数据输出

3.6.1OutputFormat接口实现类 OutputFormat是MapReduce输出的基类&#xff0c;所有实现MapReduce输出都实现了OutputFormat接口。下面我们介绍几种常见的OutputFormat实现类。 1、文本输出TextOutputFormat 默认的输出格式是TextOutputFormat&#xff0c;它把每条记录写为文…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...

GAN模式奔溃的探讨论文综述(一)

简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...