当前位置: 首页 > 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;它把每条记录写为文…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

conda相比python好处

Conda 作为 Python 的环境和包管理工具&#xff0c;相比原生 Python 生态&#xff08;如 pip 虚拟环境&#xff09;有许多独特优势&#xff0c;尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处&#xff1a; 一、一站式环境管理&#xff1a…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践&#xff0c;很多人以为AI已经强大到不需要程序员了&#xff0c;其实不是&#xff0c;AI更加需要程序员&#xff0c;普通人…...

写一个shell脚本,把局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

写一个shell脚本&#xff0c;把局域网内&#xff0c;把能ping通的IP和不能ping通的IP分类&#xff0c;并保存到两个文本文件里 脚本1 #!/bin/bash #定义变量 ip10.1.1 #循环去ping主机的IP for ((i1;i<10;i)) doping -c1 $ip.$i &>/dev/null[ $? -eq 0 ] &&am…...

高效的后台管理系统——可进行二次开发

随着互联网技术的迅猛发展&#xff0c;企业的数字化管理变得愈加重要。后台管理系统作为数据存储与业务管理的核心&#xff0c;成为了现代企业不可或缺的一部分。今天我们要介绍的是一款名为 若依后台管理框架 的系统&#xff0c;它不仅支持跨平台应用&#xff0c;还能提供丰富…...