C++STL之list的模拟实现

news2024/9/29 18:10:06

目录

一.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的模拟实现就到此为止了.

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

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

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

相关文章

MySql触发器学习

文章目录1 触发器1.1介绍1.2 创建触发器1.2 删除触发器1.3查看触发器1 触发器 1.1介绍 触发器是与表有关的数据库对象&#xff0c;指在 insert/update/delete 之前或之后&#xff0c;触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的…

(十八)操作系统-进程互斥的软件实现方法

文章目录一、知识总览二、单标志法三、双标志先检查法四、双标志后检查法五、Peterson算法六、总结一、知识总览 二、单标志法 算法思想&#xff1a;两个进程在访问临界区后&#xff0c;会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进…

Guna Charts WinForm 1.0.8 Crack

Guna Charts 16 图表 在 16 种不同的图表类型中可视化您的数据。 Guna Charts 反应灵敏 轻松响应屏幕尺寸的变化。 Guna Charts 实时图表 创建实时数据仪表板现在非常容易。 Guna Charts 混合图表类型 混合多种图表类型&#xff0c;例如条形图和折线图/面积图。 Guna Charts…

MS9122是一款USB单芯片投屏器,内部集成了USB2 0 控制器和数据收发模块、HDMI 数据接口和音视频处理模块。MS9122可以通过USB接口显示

MS9122是一款USB单芯片投屏器&#xff0c;内部集成了USB2.0 控制器和数据收发模块、HDMI 数据接口和音视频处理模块。MS9122可以通过USB接口显示或者扩展PC、智能手机、平板电脑的显示信息到更大尺寸的显示设备&#xff0c;支持HDMI视频接口。 主要功能特征 HDMI v1.4兼容 最大…

【java基础】包装类,自动装箱和自动拆箱

文章目录基本介绍包装类自动装箱自动拆箱包装类注意事项包装类比较包装器内容不可变基本介绍 有时&#xff0c;需要将int这样的基本类型转换为对象。所有的基本类型都有一个与之对应的类。 例如&#xff0c;Integer类对应基本类型int。通常&#xff0c;这些类称为包装器&#…

网上这么多IT的培训机构,我们该怎么选?

说实话&#xff0c;千万不要把这个答案放在网上来找&#xff0c;因为你只能得到别人觉得合适的或者机构的广告&#xff1b;当然个人的培训经历可以听一听的&#xff0c;毕竟不靠谱的机构也有&#xff0c;比如让你交一两万去上线上课程或者一百号来人坐一起看视频&#xff0c;这…

【django】django-simpleui配置后,后台显示空白页解决方法

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 django后台显示空白页解决方法 1. 正文 添加完simpleui以后&#xff0c;后台显示一片空白&#xff0c;一脸问号&#xff1f;&#xff1f;&#xff1f; …

MacBook Pro 休眠后五国,自动重启报错

看了网上很多情况&#xff0c;都说是系统的原因&#xff0c;2018年款的Mac已经过了保修期去过天才吧说花4k 换主板解决&#xff0c;花个几千块去解决这个问题不如折旧加钱再买个新的总之先用应急的办法1.电池偏好设置&#xff0c;接通电源时勾选“ 当显示器关闭时&#xff0c;防…

类的加载过程(生命周期)

类的加载过程(生命周期) 一、装载&#xff1a;通过一个类的全限定名获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象&#xff08;将字节码加载到内存中&#xff09;&#xff0c;作为…

扫地机器人(蓝桥杯C/C++)

题目描述 小明公司的办公区有一条长长的走廊&#xff0c;由 NN 个方格区域组成&#xff0c;如下图所示。 走廊内部署了 KK 台扫地机器人&#xff0c;其中第 ii 台在第 A_iAi​ 个方格区域中。已知扫地机器人每分钟可以移动到左右相邻的方格中&#xff0c;并将该区域清扫干净。…

DevOps落地与转型:提升研发效能的方法与实践

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作者&#x1f3c6;&#xff0c;阿里云专家博主&#x1f3…

使用 OpenCV 将图像转换为铅笔素描的 8 个步骤

使用 OpenCV 从彩色图像创建铅笔素描图像这个项目是我为 LetsGrowMore 的数据科学实习生创建的任务。LetsGrowMore &#xff1a;https://letsgrowmore.in/vip/目录什么是 OpenCV&#xff1f;第 1 步&#xff1a;读取图像第 2 步&#xff1a;将图像转换为灰度第 3 步&#xff1a…

MarkDown中写UML图的方法

目录序UML图之顺序图顺序图的四个要素关于消息箭头的语法Mermaid中顺序图的简单例子样例用小人表示对象为对象设置别名激活对象UML图之类图类图中常见的关系关于不同类型关系的语法Mermaid中类图的简单例子样例类定义的两种方式为类定义成员双向关系的表示多重性关系的表示UML之…

毕业设计 基于51单片机的手机蓝牙控制8位LED灯亮灭设计

基于51单片机的手机蓝牙控制8位LED灯亮灭设计1、项目简介1.1 系统构成1.2 系统功能2、部分电路设计2.1 STC89C52单片机核心系统电路设计2.2 LED电路设计2.3 蓝牙模块电路设计3、部分代码展示3.1 定时器初始化以及中断处理3.2 串口初始化3.3 串口中断处理1、项目简介 选题指导&…

微信授权登录流程以及公众号配置方法(golang后端)

一、准备一个已经认证OK的微信公众号和已经备案的域名&#xff0c;且解析好配置好https证书。 1.如上图 微信公众号 > 基本配置 &#xff0c;设置开发者密码 2.设置IP白名单&#xff0c;白名单填写提供后端服务的服务器公网IP 二、公众号服务器配置。 1.找到基本配置 2.将服…

C语言基础应用(二)数据的转换与输入输出

学习了C语言的基本数据类型后&#xff0c;我们可能会想这些数据如何进行运算&#xff0c;是否可以让不同类型的数据直接进行运算呢&#xff1f; 一、数据类型转换 1.1 int类型与float类型之间的转换 int i 5; // j值为2.000000 因为左右操作数均为整型float j i/2; // …

人脸识别原理与模型方法综述

概述 一、人脸识别流程 二、优点特性 1、便捷性 采集设备简单&#xff0c;使用快捷。一般来说&#xff0c;常见的摄像头就可以用来进行人脸图像的采集&#xff0c; 不需特别复杂的专用设备。图像采集在数秒内即可完成。 2、友好性 通过人脸识别身份的方法与人类的习惯一致&am…

C# 用NPOI读取EXCEL

1. 复制DLL文件 ICSharpCode.SharpZipLib.dll NPOI.dll NPOI.OOXML.dll NPOI.OpenXml4Net.dll NPOI.OpenXmlFormats.dll 2. 在工程中添加引用 3. using System.IO; using NPOI.HSSF.UserModel; using NPOI.XSSF.UserModel; using NPOI.SS.UserModel; using NPOI.OpenXml4Ne…

顿悟日记(一)

目录2023年1月顿悟日记&#xff1a;2023年2月24日顿悟日记&#xff1a;2023年2月25日顿悟日记&#xff1a;2023年2月26日顿悟日记&#xff1a;顿悟的经历是如此的奇妙&#xff0c;且让人亢奋的事情。 2023年1月顿悟日记&#xff1a; 1.我是面向对象还是面向过程&#xff1f; …

Linux下使用Shell脚本实现进程监控

本文介绍一种在Linux系统下为实现某些关键进程状态的实时监控而使用shell脚本的编写方法。在这里主要通过监控某些进程是否退出作为判断依据&#xff0c;如果某个进程退出了&#xff0c;则进行对应的恢复处理&#xff0c;如重新拉起相关的进程等。下面介绍该脚本的实现流程。首…