【STL】list的模拟实现

目录

前言 

list概述

list的节点

list的迭代器 

list的结构 

构造与析构

拷贝构造与赋值

list的元素操作 

insert()

push_back() 

 push_front()

erase() 

pop_back()

pop_front()

clear()

swap()

size()

完整代码链接 


前言 

如果你对链表还不熟悉或者忘了的话,建议你可以回去复习一下或者看一下这篇文章:双向链表

如果你没看过前几篇vector的模拟实现和string的模拟实现也建议可以去看看,因为这里有些内容在前面讲过了,所以解释的篇幅就比较少了。

如果内容有错,还望指出

希望本篇文章能对你学习STL有所帮助。

list概述

相较于vector,list就显得复杂许多,它的好处是每次插入或删除一个元素,就配置或释放一个元素空间。list的底层其实是一个双向链表的结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素,所以list是可以在常数范围内的任意位置进行插入和删除的序列式容器,并且可以前后迭代。但是list的最大的缺陷是不支持任意位置的随机访问。

list的节点

list本身和list节点的结构是不同的,所以我们需要分开写,下面是list的节点结构,很明显它是个双向链表。

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

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

list的迭代器 

可以说list的精华就在这迭代器上。list的迭代器的设计不能再向vector一样用个普通指针作为迭代器,因为对于链表来每个节点之间空间是不连续的,并且我们必须要让list的迭代器正确的指向list的节点,并且能够完成迭代器的一系列操作(取值、++、--等)。

所以我们的list迭代器初步设计如下

    template <class T>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T> iterator;

		Node* _node;

        //初始化
		__list_iterator(Node* node)
			:_node(node)
		{}

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

		bool operator==(const iterator& it)const
		{
			return _node == it._node;
		}

		//*it
		T& operator*()
		{
			return _node->_data;
		}

		//it->
		T* operator->()
		{
			return &(operator*());
		}

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

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

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

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

为了前置++和后置++、前置--和后置--之间能构成函数重载,所以我们需要在参数上动手脚,我这里在参数上就加了个int,包括源码中也是这样实现的。

对于->访问,源码里的实现并结合对应的场景来看,就有点让人难以看懂了

struct Coord
{
	Coord(int a1 = 0, int a2 = 0)
		:_a1(a1)
		,_a2(a2)
	{}
	int _a1;
	int _a2;
};

int main()
{
	hjx::list<Coord> lt;
	lt.push_back(Coord(10, 20));//匿名对象
	lt.push_back(Coord(21, 11));//匿名对象
	auto it = lt.begin();
	while (it != lt.end())
	{
		cout << it->_a1 << ":" << it->_a2 << endl;
		it++;
	}
	return 0;
}

其实这里为了语言的可读性,编译器做了特殊处理,省略了一个->,所以没做特殊处理前应该是这样写的:it->->_a1,前一个->是调用了it.operator->()。

基于上面的迭代器的设计,对于const迭代器我们需要在operator*()和operator->()返回值加上const,但是这样写的话只有返回值不同不构成重载,当然你可以为const迭代器重新弄一个类出来。我们可以看看源码中是怎样设计的

源码中把T&和T*单领出来进行模板的实例化,如果模板实例化出const类型那这个迭代器就是const迭代器。

所以我们将迭代器重新设计如下

    template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> Node;
		typedef __list_iterator<T, Ref, Ptr> iterator;

		Node* _node;

		__list_iterator(Node* node)
			:_node(node)
		{}

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

		bool operator==(const iterator& it)const
		{
			return _node == it._node;
		}

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

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

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

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

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

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

list的结构 

    template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		//迭代器
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T, const T&, const T*> const_iterator;
		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);
		}
		
	private:
		Node* _head;
	};

 list在源码中的实现其实就是带头节点的双向循环链表

构造与析构

 构造函数

对于只有一个哨兵位来说的话只要让next和prev都指向自己就好了。

		//对哨兵位的头结点进行初始化
		void empty_Init()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		
		list()
		{
			empty_Init();
		}

		//迭代器区间构造
		template<class Inputiterator>
		list(Inputiterator first, Inputiterator last)
		{
			empty_Init();
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}

析构函数 

调用clear函数就行,最后不要忘记哨兵位也要释放掉。 

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

拷贝构造与赋值

这里就提供现代的写法啦,毕竟现代写法更简单。

有些细心的同学可能在C++文档中看到拷贝构造的接口时会省略掉模板参数,这是被C++所允许的,在类里面可以这样,但在类外可不能省略。建议初学者还是不要去省略这里的模板参数了。

 

		list(const list<T>& lt)
		{
			empty_Init();
			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

list的元素操作 

如果你数据结构学的非常扎实的话,这部分内容对你来说应该是小菜一碟。 

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);
		}

push_back() 

        void push_back(const T& x)
		{
			insert(end(), x);
		}

 push_front()

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

erase() 

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

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			return iterator(next);
		}

pop_back()

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

pop_front()

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

clear()

        void clear()
		{
			//写法一
			//Node* cur = _head->_next;
			头删
			//while (cur != _head)
			//{
			//	_head->_next = cur->_next;
			//	delete cur;
			//	cur = _head->_next;
			//}
			//_head->_next = _head->_prev = nullptr;

			//写法二
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//erase返回的是下一个位置的迭代器
			}
		}

swap()

		void swap(list& x)
		{
			std::swap(_head, x._head);
		}

size()

		size_t size()const
		{
			Node* cur = _head->_next;
			size_t count = 0;
			while (cur != _head)
			{
				count++;
				cur = cur->_next;
			}

			return count;
		}

 关于其它函数有兴趣可以自己动手去实现一下,这里就不在展示了。

完整代码链接 

代码链接:list

源码链接:STL源码 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手机银行客户端框架之mPaaS介绍

移动开发平台&#xff08;Mobile PaaS&#xff0c;简称 mPaaS&#xff09;是源于支付宝 App 的移动开发平台&#xff0c;为移动开发、测试、运营及运维提供云到端的一站式解决方案&#xff0c;能有效降低技术门槛、减少研发成本、提升开发效率&#xff0c;协助企业快速搭建稳定…

Harmony鸿蒙南向驱动开发-SPI

SPI即串行外设接口&#xff08;Serial Peripheral Interface&#xff09;&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线。SPI是由Motorola公司开发&#xff0c;用于在主设备和从设备之间进行通信。 运作机制 在HDF框架中&#xff0c;SPI的接口适配模…

#5松桑前端后花园周刊-JavaScript引擎和JavaScript运行时之间的区别

行业动态 TC39 Signals 提案 一个早期提案&#xff1a;给 ECMAScript/JavaScript 带来一个新特性 signals&#xff0c;该提案从一系列流行的框架中引入了一些想法。提案解释 signals 是一种数据类型&#xff0c;它通过模拟状态单元和从其他状态/计算中派生的计算来实现单向数…

免费ssl证书能一直续签吗?如何获取SSL免费证书?

免费SSL证书是否可以一直续签。我们需要了解SSL证书的基本工作原理。当你访问一个使用HTTPS协议的网站时&#xff0c;该网站实际上在使用一个SSL证书。这个证书相当于一个数字身份证明&#xff0c;它验证了网站的真实性和安全性。而这个证明是由受信任的第三方机构——通常是证…

jvm中jdk常用的几个命令总结

1.jmap 此命令可以用来查询内存信息&#xff0c;实例个数及占用内存大小 1.1 查看堆内存概要信息&#xff08;内存分配统计&#xff09; jmap -histo[:live] <pid> .-histo&#xff1a;显示堆中对象的统计信息&#xff0c;包括每个类的实例数量、占用内存大小等 :live…

Vue+el-table 修改表格 单元格横线边框颜色及表格空数据时边框颜色

需求 目前 找到对应的css样式进行修改 修改后 css样式 >>>.el-table th.el-table__cell.is-leaf {border-bottom: 1px solid #444B5F !important;}>>>.el-table td.el-table__cell,.el-table th.el-table__cell.is-leaf {border-bottom: 1px solid #444B5F …

【开源社区】openEuler、openGauss、openHiTLS、MindSpore

【开源社区】openEuler、openGauss、openHiTLS、MindSpore 写在最前面开源社区参与和贡献的一般方式开源技术的需求和贡献方向 openEuler 社区&#xff1a;开源系统官方网站官方介绍贡献攻略开源技术需求 openGauss 社区&#xff1a;开源数据库官方网站官方介绍贡献攻略开源技术…

机器学习和深度学习--李宏毅 (笔记与个人理解)Day7

Day7 Regression Case study &#xff08;预测宝可梦的cp&#xff09; Regression 可以做什么&#xff1f; 股票预测 自动驾驶 推荐 预测宝可梦的cp&#xff08;能力类似这样的属性把&#xff09; 这里突然想到&#xff0c;是不是可以用洛克王国和赛尔号做事情哈哈 注意&#…

解决苹果iMac的M1芯片Node Sass does not yet support your current environment的问题

问题背景 如图所示&#xff0c;这是我的电脑&#xff0c;M1芯片 启动前端项目老是报错&#xff0c;说node Sass不支持我当前的环境&#xff0c;同事的macBook是intel芯片的&#xff0c;就能跑起项目来 很烦 但是不慌&#xff01;&#xff01;&#xff01; 咱有解决方法啦&a…

【C 数据结构】线性表

文章目录 【 1. 线性表 】【 2. 顺序存储结构、链式存储结构 】【 3. 前驱、后继 】 【 1. 线性表 】 线性表&#xff0c;全名为线性存储结构&#xff0c;线性表结构存储的数据往往是可以依次排列的&#xff08;不考虑数值大小顺序&#xff09;。 例如&#xff0c;存储类似 {1…

Visual Studio C++ 正确创建项目与更改文件名

1、创建项目 1&#xff09;打开Visual Studio&#xff0c;选择创建新项目。 2&#xff09;创建空项目 3&#xff09;配置新项目&#xff0c;注意不要勾选 " 将解决方案和项目放在同一目录中 " 。并将位置的文件夹设为与解决方案同名&#xff0c;方便管理。项目名称则…

客户关系CRM管理系统源码 企业crm管理系统

客户关系CRM管理系统源码 企业crm管理系统 系统功能介绍 1、 公海管理&#xff1a;公海类型、客户公海。 2、 线索管理&#xff1a;我的线索、线索列表、线索状态、线索来源。 3、 客户管理&#xff1a;我的客户、客户列表、成交客户、行业类别、预查、地区列表、客户状态、…

Docker Compose 一键安装

文章目录 一、场景说明二、脚本职责三、参数说明四、操作示例五、注意事项 一、场景说明 本自动化脚本旨在为提高研发、测试、运维快速部署应用环境而编写。 脚本遵循拿来即用的原则快速完成 CentOS 系统各应用环境部署工作。 统一研发、测试、生产环境的部署模式、部署结构、…

SOCKS代理是如何增强网络隐私?

在数字化时代&#x1f310;&#xff0c;网络隐私的重要性日益凸显。个人和组织都在寻找有效的方法来保护自己的网络活动不受侵犯。SOCKS代理作为一种流行的网络协议&#xff0c;提供了一种有效的手段来增强网络隐私。本文将详细介绍SOCKS代理是如何工作的&#xff0c;以及它是如…

BPMN.JS中文教程学习

基础篇 vue bpmn.js 建模BpmnModeler将数据转图形bpmnModeler.importXML // basic.vue<script>// 引入相关的依赖import BpmnModeler from bpmn-js/lib/Modelerimport {xmlStr} from ../mock/xmlStr // 这里是直接引用了xml字符串export default {name: ,components: {…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.3 月末操作:外币评估

2.6.3 月末操作&#xff1a;外币评估 企业的外币业务在记账时一般使用期初的汇率或者即时汇率&#xff0c;但在月末&#xff0c;需要按照月末汇率对外币的余额或者未清项进行重估&#xff08;revaluation&#xff09;。 企业在资产负债表日&#xff0c;应当按照下列规…

二百三十、MySQL——MySQL表的索引

1 目的 梳理一下目前MySQL维度表的索引情况&#xff0c;当然网上也有其他博客专门讲MySQL索引的&#xff0c;我这边只是梳理一下目前的索引状况而已 2单列索引 2.1 索引截图 2.2 建表语句 3 联合索引 3.1 索引截图 3.2 建表语句 4 参考的优秀博客 http://t.csdnimg.cn/ZF7…

ENSP防火墙配置策略路由及ip-link探测

拓扑 配置目标 1.A区域走ISP1&#xff0c;B区域走ISP2 2. isp线路故障时及时切换到另一条线路 配置接口及安全区域 配置安全策略 配置nat 配置默认路由 配置ip-link 配置策略路由 cl-1 cl-2 验证配置成功 策略路由 A走ISP1 B走ISP2 验证线路故障 isp1 in g0/0/0 shoutdow…

Cosmopolitan Libc 工作原理与多平台使用(x64 Linux / WSL2 / Windows)

⚠️阅读前请注意 本博客适用于Cosmopolitan Libc 3.X版本&#xff0c;不适用于Cosmopolitan Libc 2.X版本。Cosmopolitan Libc 是一个非常年轻的项目&#xff0c;可能存在各种问题。Cosmopolitan Libc 仍处于快速迭代开发之中&#xff0c;本文内容在一定时期内会持续更新。 Co…

小程序项目思路分享爬虫

小程序项目思路分享爬虫 具体需求&#xff1a; 有这几个就行&#xff0c;门店名称门店地址门店类型&#xff0c;再加上省、市、县/区门店名称&#xff1a;storeName 门店地址&#xff1a;storeAddress 程序运行&#xff1a; honor_spider获取经纬度信息。 经纬度——>详…