【C++初阶】list的模拟实现 附源码

2023-11-10

一.list介绍

list底层是一个双向带头循环链表,这个我们以前用C语言模拟实现过,->双向带头循环链表

下面是list的文档介绍: list文档介绍

我们会根据 list 的文档来模拟实现 list 的增删查改及其它接口。


 

二.list模拟实现思路

既然是用C++模拟实现的,那么一定要封装在类里

为了适合各种类型的数据,会使用模板

节点 Node

了解双向循环带头链表的都知道,我们需要一个节点 (Node),之前用C语言实现的时候,我们写了一个叫做 BuynewNode 的函数来获取节点,而在C++里我们用类封装一个,注意这个用 struct 封装比较好,因为 struct 默认是公有的,这样方便我们访问,所以可以写一个类:

    struct  list_node

迭代器  iterator

我们知道,C++提供了一种统一的方式来访问容器,这就是迭代器,string 和 vector 的迭代器模拟实现很简单,因为 string 和 vector 底层是用数组实现的,数组是一段连续的物理空间,支持随机访问,所以它是天然的迭代器

但是链表不一样,它不是一段连续的物理空间,不支持随机访问,所以想让 list 的迭代器在表面上和 string,vector 的迭代器用起来没有区别,我们在底层上就需要用类封装迭代器,然后再迭代器类的内部,重载  ++  --  *  ->  !=  ==  这些迭代器会用到的运算符

所以创建一个迭代器类:

   struct  list_iterator

const 迭代器  const_iterator

实现的普通的迭代器,还有 const 迭代器,const 迭代器的意思是让指针指向的内容不变,而指针本身可以改变,例如指针++,指针-- 这种操作,所以 const 迭代器与普通迭代器的不同只有 重载 * 运算符的返回值不同,它是 const  T&  (T是模板参数),重写一个const 迭代器类又显得太冗余,代码的可读性就降低了;

前面在学习模板时,我们知道不同的模板参数,编译器会生成不同的函数,所以我们选择加一个模板参数 :Ref 。这样只要在显示实例化模板参数时:

              普通迭代器就传 T&

              const 迭代器就传 const T&

-> 运算符重载

看下面这段代码:

using namespace std;

struct A
{
	A(int a1,int a2)
		:_a1(a1)
		,_a2(a2)
	{}

	int _a1;
	int _a2;
};

void test_list()
{
	list<A> lt;   //实例化自定义类型
	lt.push_back(A(1, 1));
	lt.push_back(A(2, 2));
	lt.push_back(A(3, 3));
	lt.push_back(A(4, 4));

	list<A>::iterator it = lt.begin();

	while (it != lt.end())
	{
		cout << it->_a1 << " " << it->_a2 << endl;  //像指针一样访问自定义类型里的成员变量
		it++;
	}	
}

int main()
{
	test_list();

	return 0;
}

有时候,实例化的模板参数是自定义类型,我们想要像指针一样访问访问自定义类型力的成员变量,这样显得更通俗易懂,所以就要重载 -> 运算符,它的返回值是 T* ,但是正常来说这里应该是这样访问的: it -> -> _a1

因为迭代器指向的是 整个自定义类型,要想再访问其内部成员应该再使用一次 -> (这个->就不是重载的 -> ,就是普通的 -> ),但是上面的代码为什么就写了一个 -> ,这个是C++语法把这里特殊化了。

那该怎么在迭代器类里重载 -> 运算符呢?

和const 迭代器一样,只需要再加一个模板参数 :Ptr

显示实例化的时候传 T* 就行了。 

迭代器类 模拟实现源码: struct list_iterator

以上的都算 list 模拟实现的难点,其他的像 重载 ++ 什么的,对于学过数据结构的小伙伴们是非常简单的,就不赘述了,没学过的可以看看这篇文章:双向带头循环链表

template<class T,class Ref,class Ptr>   //三个模板参数
	struct list_iterator   //封装迭代器
	{
		typedef list_node<T> Node;  //重命名节点
		typedef list_iterator<T, Ref, Ptr> self;  //重命名迭代器类型
		Node* _node;

		list_iterator(Node*node)   //构造函数,单参数的构造函数支持隐式类型转换
			:_node(node)
		{}

		//重载 * ++ -- != == ->
		Ref operator*() const
		{
			return _node->_val;
		}

		Ptr operator->() const
		{
			return &_node->_val;
		}

		self& operator++()   //前置++
		{
			_node = _node->_next;

			return *this;
		}

		self operator++(int)  //后置++
		{
			self tmp = *this;
			_node = _node->_next;

			return tmp;
		}

		self& operator--()   //前置--
		{
			_node = _node->_prev;

			return *this;
		}

		self operator--(int)  //后置--
		{
			self tmp = *this;
			_node = _node->_prev;

			return tmp;
		}

		bool operator!=(const self& lt) const
		{
			return _node != lt._node;
		}

		bool operator==(const self& lt) const
		{
			return _node == lt._node;
		}

	};

list

我们在用C语言实现双向带头循环链表时,会先初始化链表的头(head),即让它的 前驱指针(prev)和后继指针(next)都指向自己

在C++的模拟实现 list 中,我们会创建一个类 list  来管理链表的节点并实现增删查改及其它接口,所以 list  的构建函数就是初始化 头(head)节点


三.源码

list.h

 

我们可以模拟实现以上接口,具体函数的逻辑可以查阅文档,实现起来都是很简单的。

namespace nagi   //把模拟实现list的类都放在一个命名空间里封装起来
{
	template<class T>
	struct list_node   //创建节点
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _val;

		list_node(const T& val = T())  //构造函数,初始化节点
			:_prev(nullptr)
			,_next(nullptr)
			,_val(val)
		{}

	};

	template<class T,class Ref,class Ptr>   //三个模板参数
	struct list_iterator   //封装迭代器
	{
		typedef list_node<T> Node;
		typedef list_iterator<T, Ref, Ptr> self;
		Node* _node;

		list_iterator(Node*node)   //构造函数,单参数的构造函数支持隐式类型转换
			:_node(node)
		{}

		//重载 * ++ -- != == ->
		Ref operator*() const
		{
			return _node->_val;
		}

		Ptr operator->() const
		{
			return &_node->_val;
		}

		self& operator++()   //前置++
		{
			_node = _node->_next;

			return *this;
		}

		self operator++(int)  //后置++
		{
			self tmp = *this;
			_node = _node->_next;

			return tmp;
		}

		self& operator--()   //前置--
		{
			_node = _node->_prev;

			return *this;
		}

		self operator--(int)  //后置--
		{
			self tmp = *this;
			_node = _node->_prev;

			return tmp;
		}

		bool operator!=(const self& lt) const
		{
			return _node != lt._node;
		}

		bool operator==(const self& lt) const
		{
			return _node == lt._node;
		}

	};

	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;  //重命名const迭代器

		void empty_init()  //因为构造函数和拷贝构造都会初始化头节点,所以就写成一个函数了
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
			_sz = 0;
		}

		list()  //构造函数
		{
			empty_init();
		}
		//普通迭代器
		iterator begin()
		{
			return _head->_next;
		}

		iterator end()
		{
			return _head;
		}
		//const迭代器
		const_iterator begin() const
		{
			return _head->_next;
		}

		const_iterator end() const
		{
			return _head;
		}

		iterator insert(iterator pos, const T& x)  //在pos之前插入
		{
			Node* newnode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			_sz++;

			return newnode;
		}

		iterator erase(iterator pos)   //删除pos位置,注意删除的时候不能把头节点也删了,所以要做pos检查
		{
			assert(pos != end());

			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

			delete cur;
			_sz--;

			return next;   //库里规定返回删除节点的下一个节点
		}

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

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

		void pop_back()  //尾删
		{
			erase(--end());
		}

		void pop_front()  //头删
		{
			erase(begin());
		}

		void clear()  //清楚除了头节点以外的所有节点
		{
			iterator it = begin();
			while (it != end())
			{
				it=erase(it);
				
			}
			_sz = 0;
		}

		~list()  //析构函数
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		list(const list<T>& lt)   //拷贝构造
		{
			empty_init();

			for (auto& e : lt)
			{
				push_back(e);
			}
			
		}

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

		list<T>& operator=(list<T> lt)  //赋值重载
		{
			swap(lt);

			return *this;
		}

	private:
		Node* _head;  //头节点
		size_t _sz;   //记录链表的长度
	};

}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++初阶】list的模拟实现 附源码 的相关文章

  • 在哪里可以找到列出 SSE 内在函数操作的官方参考资料?

    是否有官方参考列出了 GCC 的 SSE 内部函数的操作 即 头文件中的函数 除了 Intel 的 vol 2 PDF 手册外 还有一个在线内在指南 https www intel com content www us en docs in
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • Asp.NET WebApi 中类似文件名称的路由

    是否可以在 ASP NET Web API 路由配置中添加一条路由 以允许处理看起来有点像文件名的 URL 我尝试添加以下条目WebApiConfig Register 但这不起作用 使用 URIapi foo 0de7ebfa 3a55
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • BitTorrent 追踪器宣布问题

    我花了一点业余时间编写 BitTorrent 客户端 主要是出于好奇 但部分是出于提高我的 C 技能的愿望 我一直在使用理论维基 http wiki theory org BitTorrentSpecification作为我的向导 我已经建
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 这些作业之间是否存在顺序点?

    以下代码中的两个赋值之间是否存在序列点 f f x 1 1 x 2 不 没有 在这种情况下 标准确实是含糊不清的 如果你想确认这一点 gcc 有这个非常酷的选项 Wsequence point在这种情况下 它会警告您该操作可能未定义
  • 在 HTML 下拉列表中有一个滚动条

    我正在寻找一种在 HTML 的下拉列表中添加滚动条的方法 这样如果下拉列表包含的内容超过例如 5 项 将出现滚动条以查看其余项 这是因为我将被迫列出一些大清单 过去几个小时我一直在谷歌上搜索它 但没有运气 它需要适用于 IE8 FF 和 C
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • 测试用例执行完成后,无论是否通过,如何将测试用例结果保存在变量中?

    我正在使用 NUNIT 在 Visual Studio 中使用 Selenium WebDriver 测试用例的代码是 我想在执行测试用例后立即在变量中记录测试用例通过或失败的情况 我怎样才能实现这一点 NUnit 假设您使用 NUnit
  • 是否可以在 .NET Core 中将 gRPC 与 HTTP/1.1 结合使用?

    我有两个网络服务 gRPC 客户端和 gRPC 服务器 服务器是用 NET Core编写的 然而 客户端是托管在 IIS 8 5 上的 NET Framework 4 7 2 Web 应用程序 所以它只支持HTTP 1 1 https le
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐

  • 学习1(Linunx操作系统_前期安装)

    1 安装虚拟机 vm12 我下载的是这个版本 vmware workstation full 12 1 0 3272444 exe 下载地址 https www xinsaisai com vmware workstation full 1
  • keep-alive源码解析及实现原理

    keep alive源码 vue 2 6 10 在src core components keep alive js中 代码分析 export default name keep alive abstract true 抽象组件 props
  • 解决Mac应用程序软件不出现在Launchpad里面的方法

    新装了几个软件 可是打开Lauchpad之后却在里面找不到 尝试重置Launchpad方式 1 分别输入终端命令即可 rm Library Application Support Dock db killall Dock
  • ASP.NET 清除模式窗口数据缓存

    使用模式窗口showModalDialog 弹出页面在asp net中经常用到 用的最多的就是点击 修改 按钮 弹出修改页面 修改成功之后 关闭修改页面 刷新父页面 目前存在的一个问题是 刷新完父页面之后 再点击修改按钮弹出修改页面 修改页
  • Java 枚举

    枚举的每一个成员变量就是枚举类型自身的一个实例 枚举的实例在编译的时候就能确定枚举类型有多少个 实例对象 每一个枚举都继承自java lang Enum类 枚举的每个成员默认都是 public static final 的 当定义一个 枚举
  • gf框架使用sqlite3数据库后交叉编译cgo适配arm64-linux

    gf框架使用sqlite3数据库后交叉编译cgo适配arm64 linux 文章目录 gf框架使用sqlite3数据库后交叉编译cgo适配arm64 linux 1 前言 2 解决方案 3 wsl Windows交叉编译cgo工程 3 1
  • 期末考试复习笔记(标红表示重要)

    目录 相关系数的比较 数据的类型 回归模型的统计检验与统计意义 参数检验 非参数检验 统计距离 量表 李克特量表 权重 聚类图分析 聚类分析简介 聚类的用途 聚类方法 两步聚类法 TwoStep Cluster 箱线图分析 中心位置的作用
  • Redis数据类型-List

    一 概述 Java中 数组 Arraylist 链表 linkedList 数组的特点 根据索引取值速度是极快的 和数据量的大小无关 数组的增删改查 效率极低 数据量越大 效率越低 链表的特点 链表的元素增删 效率极高 和数据量的大小无关
  • 超详细!Jmeter性能测试(一)

    Jmeter 性能测试 一 首先开发会给你一个接口文档 我们这边是做支付方面的 所以我们要求给下单支付接口做下压测 由于我们这边接口都是有加密参数的 所以都是直接在JAVA工程包里直接跑的 因为这次是做压测 所以我们要用上Jmeter这个工
  • VC++ OpenCV4.x二维码识别

    自OpenCV4 x开始 二维码识别已经悄然进入 再也不用看zbar脸色了 以下是官网发布的源码 include opencv2 objdetect hpp include opencv2 imgproc hpp include openc
  • Node.Js篇 NodeJs使用MongoDB

    目录 介绍 概念解析 安装 启动时注意事项 NodeJs操作Mongo 介绍 MongoDB 是一个基于分布式文件存储的数据库 由 C 语言编写 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案 MongoDB 是一个介于关系数据库和
  • 酷开科技打造更好体验服务用户

    智能电视以其海量资源 智慧大屏 高清画质等特点在国内快速普及 然而 随着用户量的增加 用户群体的需求多元化 导致消费者对智能电视的应用要求越来越高 不仅希望智能电视内容丰富 最好还能拥有 多合一 的功能 好在 一些科技企业关注到市场痛点 致
  • 全连接层、卷积层、深度可分离卷积的参数量计算

    一 全连接层参数的计算 若输入大小为32 32 3的图片 第一层全连接层有500个节点 则地一层全连接网络的个参数量为 32 32 3 500 500 约为150万个参数 参数量多 导致计算速度缓慢且容易造成过拟合 于是卷积操作便横空出世
  • Taro编译微信小程序实现顶部自定义导航栏

    需求 使用taro开发微信小程序的过程中 涉及到小程序的需要自定义顶部导航栏 导航栏渐变色 微信小程序中只能够设置固定的颜色 渐变颜色以及添加其他按钮的操作就不能够通过小程序自带的api来实现 思路 配置自定义导航栏设置 获取顶部状态栏高度
  • 一个进程可以创建多少线程?

    理论上 一个进程可用虚拟空间是2G 默认情况下 线程的栈的大小是1MB 所以理论上一个进程可以创建2048个线程 当然更改编译器的设置可以创建多余2048个线程 因此 一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定 只要虚拟
  • PTA Python习题 计算工资

    题目要求 编写函数pay 带两个输入参数 小时工资和上周员工工作了的小时数 函数计算并返回员工的工资 加班工资的计算方法如下 大于40小时但小于或等于60小时按平时小时薪酬的1 5倍给薪 大于60小时则按平时小时薪酬的2倍给薪 函数接口定义
  • 【恒指早盘分析】期货交易绝非你想的那么简单

    对期货而言 这个市场是绝对平等的 它不需要八面玲珑的关系 不靠权势 只凭借勤奋努力来实现梦想 实现真正的财务自由 因此 对每一位立志于靠智慧生活的人来说 期货投资是一个极好的发展领域 从平时的练习和实践中 可以得到身 心 技的全面塑造和修行
  • Google Mock - GoogleTest(九)

    本文翻译自 https github com google googletest blob master googlemock docs CheatSheet md 一 定义一个模拟类 1 模拟一个正常的类 就是接口类 给 1 2 3 4
  • 数字经济时代下的软硬件基础设施建设与发展

    随着全球数字化新时代的到来 软件正在被重新定义 程序员的世界的代码走向各行各业 智慧城市 载人航天 潜海探月 数字新时代的加快到来 也为开发者拥有无限想象力提供了新机遇 一 云计算 云计算 大数据和人工智能 这三个东西已非常火 并且它们之间
  • 【C++初阶】list的模拟实现 附源码

    一 list介绍 list底层是一个双向带头循环链表 这个我们以前用C语言模拟实现过 gt 双向带头循环链表 下面是list的文档介绍 list文档介绍 我们会根据 list 的文档来模拟实现 list 的增删查改及其它接口 二 list模
Powered by Hwhale