C++——list的实现以及源码

前言:

最近学习了c++list的实现,这让我对迭代器的理解又上升了一个新的高度,注意:代码里的list是放在一个叫zgw的命名空间里边,但是在实现list的代码中没有加namespace,这里给个注意,以后复习时能看懂。

list的节点:

我们首先需要搞出一个list的节点,来存储数据,以及节点的下一个指针,和节点的上一个指针,STL库里边这样设计的,所以我们这样设计:

	template< class T>
	struct ListNode
	{
		struct ListNode<T>* _next; 
		struct ListNode<T>* _prev;
		T _data;
		ListNode(const T& data = T())   //这里给的是匿名对象
			:_next(nullptr),
			_prev(nullptr),
			_data(data) {};
	};

设计这个节点应该是没有什么难度的。
 

包装list类:
 

template<class T>
class list
{
	typedef ListNode<T> node;
public:
	list() :_head(new node())
	{
		_head->_next = _head;
		_head->_prev = _head;
	}
private:
	node* _head;
};

为了让我们的list更加具有可读性,我们将,ListNode<T>重命名为node。然后我们要给一个list的无参构造,相信到现在都能看得懂。

迭代器的设计:

前方高能!!!

然后就是上强度的地方,我们需要设计list的迭代器。我们先分析一下list指针与vector的指针又什么区别呢?

首先我们list存的是ListNode<T>节点的指针,每一个新的节点都是new出来的,而且是通过指针将他们链接起来,他在物理层面上就不是连续的。

所以当我们解引用就不是一个内置类型,或者是已经设计好的自定义类型,它就没有了重载运算符,因为我们的节点就是我们自己定义的自定义类型。就算是迭代器也必要支持重载运算符。

我们举一个例子
vector的例子,当我们要搞一个存储int数据的vector容器。

void Test4()
{
	vector<int> v1 = { 1,2,3,4 };
	vector<int>::iterator it1 = v1.begin();
	cout << *it1 << endl;
}

这里我们是存储int内置类型,当我们解引迭代器时,实际是解引用int类型的指针,然后也支持流插入,所以可以直接打印第一个元素。
 

我后我们再想我们如果要用vector存储一个我们自己搞的一个类型,是否还支持流插入呢?

class aa
{
	aa(int a=10,int b=20):
		_a(a),_b(b)
	{}
private:
	int _a;
	int _b;
};

void Test4()
{
	vector<aa> v1 ;
	aa a1;
	v1.push_back(a1);
	vector<aa>::iterator it1 = v1.begin();
	cout << *it1 << endl;
}

上面这个代码会不会报错呢?当然肯定会的,为什么呢?

就是因为我们没有重载流插入。

所以通过这个例子我们应该明白了,我们ListNode本生就是一个我们自己设计的类型,所以当我们在list返回ListNode指针再解引用时肯定是不行的,因为我们没有为ListNode设计重载运算符,但是我们在这有一个新的思路我们能不能再设计一个类,当做list的迭代器,这个类迭代器存ListNode类型的指针。

在第一次听到这个设计我也是很懵逼的,都是再通过我看了两遍教学课程,再加上自己研究了一个下午,终于是领悟了其中的原理,所以想好好理解还是得自己研究。

设计的源码:


namespace zgw
{
	//结构体定义
	template< class T>
	struct ListNode
	{
		struct ListNode<T>* _next;
		struct ListNode<T>* _prev;
		T _data;
		ListNode(const T& data = T())
			:_next(nullptr),
			_prev(nullptr),
			_data(data) {};
	};
		

	template <class T,class Ref,class Ptr>//我们设计三个参数的模板迭代器,以便我们返回对应的指针
	struct  ListIterator                  //和引用(在这里的我们需要设计const_iterator,以及
	{                                     //iterator两种类型的迭代器,然后我们再通过typedef
		typedef ListNode<T> node;         //封装一下,提高可读性
		typedef ListIterator<T,Ref,Ptr> Self;

		ListIterator(node*head) :
		_node(head)
		{};

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

		Self operator++()
		{
			_node = _node->_next;
			return *this;
		}
		
		Self operator++(int)
		{
			Self re(_node);
			_node = _node->_next;
			return re;
		}

		bool operator==(const Self&node)
		{
			return _node == node._node;
		}

		bool operator!=(const Self& node)
		{
			return _node != node._node;
		}
		
		node* _node;
	};



	//封装链表,这只是一个头节点,当我们返回begin(),和end()时,
    //我们其实返回的是一个ListNode<T>类型的指针
	template<class T>
	class list
	{
		typedef ListNode<T> node;
	public:
		typedef ListIterator<T,T&,T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;
		list() :_head(new node())     //当我们需要什么类型的迭代器就传对应的typedef
		{
			_head->_next = _head;
			_head->_prev = _head;
		}

		const_iterator cbegin()
		{
			return const_iterator(_head->_next);
		}

		const_iterator cend()
		{
			return const_iterator(_head);
		}

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

		iterator end()
		{
			return iterator(_head);
		}

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

		iterator insert(iterator pos,const T&data)
		{	
			node* cur = pos._node;
			node* newnode = new node(data);
			node* prev = cur->_prev;
			newnode->_next = cur;
			prev->_next = newnode;
			newnode->_prev = prev;
			cur->_prev = newnode;
			return iterator(newnode);
		} 

		iterator erase(iterator pos)
		{
			assert(pos != end()._node);
			node* cur = pos._node;
			node* prev=cur->_prev;
			node* next = cur->_next;
			prev->_next = next;
			next->_prev = prev;
			delete cur;
			return iterator(next);
		}
		
		void pop_back()
		{
			assert(_head != begin()._node);
			node* tail= end()._node->_prev;
			node* _head = end()._node;
			tail->_prev->_next = _head;
			_head->_prev = tail->_prev;
			delete tail;
		}

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

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

	private:
		node* _head;
	};
}

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

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

相关文章

整理了10个靠谱且热门的赚钱软件,适合普通人长期做的赚钱副业

作为一名普通的上班族&#xff0c;我们每天都在辛勤工作&#xff0c;但工资的增长速度却如同蜗牛般缓慢。不过&#xff0c;别担心&#xff0c;信息时代总是带给我们无尽的惊喜&#xff01;今天&#xff0c;我将为大家推荐一些赚钱的宝藏软件&#xff0c;让你在闲暇之余轻松实现…

五分钟搭建一个Suno AI音乐站点

五分钟搭建一个Suno AI音乐站点 在这个数字化时代&#xff0c;人工智能技术正以惊人的速度改变着我们的生活方式和创造方式。音乐作为一种最直接、最感性的艺术形式&#xff0c;自然也成为了人工智能技术的应用场景之一。今天&#xff0c;我们将以Vue和Node.js为基础&#xff…

三十六计的笔记

系列文章目录 三十六计的笔记 文章目录 系列文章目录1、瞒天过海2、围魏救赵3、借刀杀人4、以逸待劳5、趁火打劫6、声东击西7、无中生有8、暗渡陈仓9、隔岸观火10、笑里藏刀11、李代桃僵12、顺手牵羊13、打草惊蛇14、借尸还魂15、调虎离山16、欲擒故纵17、抛砖引玉18、擒贼擒王…

牛客NC302 环形数组的连续子数组最大和【中等 动态规划 Java/Go/PHP/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/e9f3282363844355aa51497c5410beee 思路 动态规划 两种情况&#xff08;首位相连的&#xff09;和首位不相连的 首尾相连的可以算最小的连续子数组得出&#xff0c;sum-就是。Java代码 import java.util.*;pub…

第20届文博会:“特别呈现”—周瑛瑾雷米·艾融双个展,著名美术评论家,批评家彭德教授对周瑛瑾作品进行评论

周瑛瑾不是学院派艺术家&#xff0c;但在彩墨画领域的天赋超出中国八大美院的同类型画家。相比具有批判意识的当代艺术&#xff0c;他的彩墨艺术如同我们这个苦难世界的创可贴和安慰剂。当我面对他的彩墨画&#xff0c;首先是惊艳&#xff0c;随之想到屈原的离骚&#xff0c;还…

slint esp32 tokio

源码&#xff1a;https://github.com/xiaguangbo/slint_esp32_tokio cpu 是 esp32c2&#xff0c;屏幕是 ili9341&#xff0c;触摸是 xpt2046&#xff0c;使用 spi 半双工 不使用DMA&#xff08;esp-rs还没支持&#xff09;&#xff0c;SPI 40M&#xff0c;240*320全屏刷新为1.5…

Windows、Linux下,基于QT的打包方法

整理这篇文档的意义在于&#xff1a;自己走了很多弯路&#xff0c;淋过雨所以想为别人撑伞&#xff0c;也方便回顾&#xff0c;仅供参考 ps: 第一次做Windows下打包&#xff0c;用了2小时&#xff0c;第二次20秒第一次做Linux(ubuntu)下打包&#xff0c;用了8小时&#xff0c;…

Linux 内核

查看内核的发行版 $ uname -r 5.4.0-150-genericcd /lib/modules/5.4.0-150-generic, 内核源码所在的位置&#xff1a;/usr/src 这里的内核源码路径&#xff08;–kernel-source-path&#xff09;即为&#xff1a; cd /usr/src/linux-headers-5.4.0-150-generic/ 临时生效: …

JMETER工具:以录制手机app为例

JMETER工具&#xff1a;以录制手机app为例子 JMETER安装和环境配置 pc需要安装jdk&#xff0c;并进行jdk的环境配置&#xff0c;安装好jdk并配置好后&#xff0c;通过命令行输入java –version出现以下界面就表示安装成功&#xff1a; &#xff08;对应的jdk版本不可太低&…

网络通信(二)

UDP通信 特点&#xff1a;无连不是先接、不可靠通信 不事先建立连接&#xff1b;发送端每次把要发送的数据&#xff08;限制在64KB内&#xff09;、接收端IP、等信息封装成一个数据包&#xff0c;发出去就不管了 java提供了一个java.net.DatagramSocket类来实现UDP通信 Dat…

第13章-循迹功能 循迹小车讲解 原理分析 STM32智能小车循迹教程 红外对管使用 PID循迹算法分析

讲解一下我们小车里面的循迹部分&#xff0c;包括红外基础使用&#xff0c;无PID循迹和有PID循迹。 第13章-循迹功能 13.1-非PID循迹功能完成 先红外对管调试 我们这里学习一下&#xff0c;如何实现循迹功能 如何才能让小车沿着黑线运动、要让小车感知到黑线的位置&#x…

【SpringBoot】SpringBoot中防止接口重复提交(单机环境和分布式环境)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 &#x1f33c;前言 &#x1f512;单机环境下防止接口重复提交 &#x1f4d5;导入依赖 &#x1f4c2;项目结构 &#x1f680;创建自定义注解 ✈创建AOP切面 &#x1f697;创建Conotroller &#x1f4bb;分布…

[CISCN 2024] Crypto部分复现

文章目录 OvOez_rsacheckin浅记一下 迟来的文章 OvO 题目描述&#xff1a; from Crypto.Util.number import * from secret import flagnbits 512 p getPrime(nbits) q getPrime(nbits) n p * q phi (p-1) * (q-1) while True:kk getPrime(128)rr kk 2e 65537 kk …

3d打印问题总结

1.打印拉丝&#xff1a;https://zhuanlan.zhihu.com/p/152221550 解决方案&#xff1a;温度过高&#xff0c;PLA材料材料喷嘴温度一般设置为200度比较合适。

string OJ题

下面分享一下string做题心得 1. 明白字符串中存储的数字为0 8 9与0 8 9 完全不同&#xff0c;字符0其实在串中存储的是48&#xff0c;要有意识的转化。字符串中如果存数字8&#xff0c;意味着存了BS&#xff08;退格&#xff09; 例如1&#xff1a; 算出结果为5&#xff0c;存…

网上打印试卷的步骤是什么

对于学生和家长来说&#xff0c;打印试卷是日常学习中的一项重要需求。那么&#xff0c;如何在网上方便地打印试卷呢&#xff1f;下面&#xff0c;就让我来为您介绍琢贝云打印的试卷打印步骤。 一、选择琢贝云打印的原因 支持多种文件格式打印&#xff0c;包括图片、PPT、PDF、…

20.SkyWalking

一.简介 SkyWalking用于应用性能监控、分布式链路跟踪、诊断&#xff1a; 参考连接如下&#xff1a; https://github.com/apache/skywalking https://skywalking.apache.org/docs/ 二.示例 通过官网连接进入下载页面&#xff1a;https://archive.apache.org/dist/skywalkin…

2024年【T电梯修理】考试内容及T电梯修理新版试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【T电梯修理】考试内容及T电梯修理新版试题&#xff0c;包含T电梯修理考试内容答案和解析及T电梯修理新版试题练习。安全生产模拟考试一点通结合国家T电梯修理考试最新大纲及T电梯修理考试真题汇总&#xff0c;…

k8s中yaml文件配置指定私有镜像仓库

1. yaml文件介绍 2. 如何快速编写yaml文件 1&#xff09;如果有已存在的pod时可以 kubectl get pod xxxxxx -oyaml 2&#xff09;直接假跑一次并查看 kubectl run xxxxxx --image镜像名 --dry-run -oyaml 3&#xff09;查看pod相关描述信息 kubectl explain pod 3. 编写…

linux 安装redis 并设置开机启动

个人实测 流程 1、第一步 先下载redis ** redis地址 https://download.redis.io/releases/选择你想要的版本 我下载的是 如下图 2、第二步:把下载的包放到linux里面 我用的是 XSHELL 和XFTP 放到/usr/local/java路径下 你可以随便放 3、第三步: ** 执行 以下命令 进行解压 t…