C++STL——list类与模拟实现

news2024/11/27 19:53:21

List

  • list
  • list的常用接口模拟实现
  • 完整代码
  • list与vector的区别

list

list是一个带头双向循环链表
list文档介绍:https://legacy.cplusplus.com/reference/list/list/
list因为是链表结构,所以没有 [] 去访问数据的方式,只有用迭代器,list当中迭代器是很重要的。
这里透彻尾插不会导致迭代器失效问题,不过删除会导致迭代器失效。
list还有一个特性,就是他的sort排序接口函数效率是低于算法库中排序的效率。
更多内容就配合模拟实现来看。

list的常用接口模拟实现

list基本结构

template<class T>
struct list_node//结点
{
	list_node* _next;
	list_node* _prev;
	T _data;
	list_node(const T& x)
		:_next(nullptr)
		,_prev(nullptr)
		,_data(x)
	{}
};

下面都是模拟list类中的结构
在list中其实控制整个链表的就是头节点,这样交换内容也是非常的方便。
list成员变量:

_head

因为list是带头双向循环链表,所以最重要的是创建头节点,这里因为后面结构需要大量重复这个动作我就单独写了个函数。

void initialize()//创建头节点
{
	_head = new node(T());
	_head->_next = _head;
	_head->_prev = _head;
}

构造函数:(无参)

list()
{
	initialize();
}

在pos位置之前插入:

void insert(iterator pos, const T& x)//在pos位置之前插入
{
	node* newnode = new node(x);
	node* cur = pos._pnode;//pos位置的结点
	node* p = cur->_prev;//pos位置之前的结点
	p->_next = newnode;
	newnode->_prev = p;
	newnode->_next = cur;
	cur->_prev = newnode;
}

删除pos位置的结点:(返回值是迭代器类型)

iterator erase(iterator pos)//删除pos位置的结点
{
	assert(pos != end());
	node* cur = pos._pnode;
	node* front = cur->_prev;
	node* later = cur->_next;
	front->_next = later;
	later->_prev = front;
	delete cur;
	return iterator(later);
}

其实完成这两个函数就很方便各种插入删除了。

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 front = begin();
	while (front != end())
	{
		front = erase(front);
	}
}

拷贝构造:

template<class _iterator>//这里如果不是模板会导致不同类中的迭代器有冲突
list(_iterator front, _iterator later)
{
	initialize();
	while (front != later)
	{
		push_back(*front);
		++front;
	}
}
list(const list<T>& it)//拷贝构造
{
	initialize();//必须创建头节点,不然交换会传过去一个空指针导致析构出现问题
	list<T>newnode(it.begin(), it.end());
	swap(newnode);
}

赋值重载:(现代写法)

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

这里传参的值不是引用,也就等于p是一个拷贝构造出来的对象,也就是说和之前赋值的对象没有任何关系,只是内容相同罢了。
所以这里直接交换就好了出了这个函数的p就会自动销毁。
交换函数:

void swap(list<T>& tmp)
{
	std::swap(_head, tmp._head);//交换头结点就好
}

这里就体现出头结点的好处了,因为空间不是连续的,是随机的,所以控制整个链表就是头结点,所以交换了头结点就等于交换了两个list。
析构函数:

~list()
{
	clear();
	delete _head;//清理头结点
	_head = nullptr;
}

C++有一个特性:
在这里插入图片描述
我们发现参数不是完整的类型(缺一个模板参数T),也就是说在类里面类名等于类型。
begin与end:

typedef _list_iterator<T, T&> iterator;//模板函数下面实现
typedef _list_iterator<T, const T&> const_iterator;
const_iterator begin()const
{
	return const_iterator(_head->_next);
}
const_iterator end()const
{
	return const_iterator(_head);
}
iterator begin()
{
	return iterator(_head->_next);
}
iterator end()
{
	return iterator(_head);
}

迭代器(非常重要)

template<class T, class cur, class prt>
	struct _list_iterator//用struct是因为里面的内容都需要公开使用
	{
		typedef list_node<T> node;
		typedef _list_iterator<T, cur, prt> coord;
		node* _pnode;//迭代器指针的本质
		_list_iterator(node* p)
			:_pnode(p)
		{}
		cur operator*()//返回值分const与非const
		{
			return _pnode->_data;
		}
		prt operator->()
		{
			return &_pnode->_data;//注意->的优先级大于&
		}
		coord& operator++()//前置++
		{
			_pnode = _pnode->_next;
			return *this;
		}
		coord& operator++(int)//后置++
		{
			coord old(*this);
			_pnode = _pnode->_next;
			return old;
		}
		coord& operator--()//前置--
		{
			_pnode = _pnode->_prev;
			return *this;
		}
		coord& operator--(int)//后置--
		{
			coord old(*this);
			_pnode = _pnode->_prev;
			return old;
		}
		bool operator!=(const coord& it)
		{
			return _pnode != it._pnode;
		}
		bool operator==(const coord& it)
		{
			return _pnode == it._pnode;
		}
	};

list因为是个链表,所以和vector,string不一样,空间不是连续的,想进行++,- -,就等于访问下一个结点或者上一个结点。
这里要注意迭代器是需要有const的:
迭代器指向的内容不能被修改,并不代表不能++,- -
所以就需要实现一份const迭代器,其实也就是解引用的不同,解引用返回的是const,和非const,其他函数一摸一样,进行函数重载是不行的,因为参数也要加const,不然无法重载,但是参数加了const其他函数就无法进行调用了,涉及到了权限放大,如果再写一个类太麻烦,这时候就可以加一个模板参数cur来决定用的时候添加什么参数。
重点是重载的->
如果list中的类型是一个自定义类型呢?

	struct test
	{
		int row;
		int col;
		test(int x = 0, int y = 0)
			:row(x)
			,col(y)
		{}
	};
	void test4()
	{
		list<test>arr;
		arr.push_back(test(1, 2));
		arr.push_back(test(3, 4));
		arr.push_back(test(5, 6));
		arr.push_back(test(7, 8));
		list<test>::iterator cur = arr.begin();
		while (cur != arr.end())
		{
			cout << cur->row << ' ';//这里如果用解引用是不可以的,因为cout需要再次进行重载才能打印出来内容
		}
		cout << endl;
	}

这时候就要用->就方便了,首先cur要是个指针才行,很明显它不是,所以返回的时候需要返回地址,并且要注意const的类型,就和解引用一样。
还有一点很奇怪:

cur->row == cur.operator->(row)

这里返回的是地址,为什么还能进行访问row呢?这是因为编译器在这里进行了特殊处理,原本面貌是这样的:

cur->->row

这样的代码看起来非常不美观,所以就进行了处理,去掉了一个->。

完整代码

	#include <iostream>
	#include <list>
	#include <assert.h>
	#include <algorithm>
	using namespace std;
	template<class T>
	struct list_node//结点
	{
		list_node* _next;
		list_node* _prev;
		T _data;
		list_node(const T& x)
			:_next(nullptr)
			,_prev(nullptr)
			,_data(x)
		{}
	};
	template<class T, class cur, class prt>
	struct _list_iterator
	{
		typedef list_node<T> node;
		typedef _list_iterator<T, cur, prt> coord;
		node* _pnode;//迭代器指针的本质
		_list_iterator(node* p)
			:_pnode(p)
		{}
		cur operator*()//返回值分const与非const
		{
			return _pnode->_data;
		}
		prt operator->()
		{
			return &_pnode->_data;//注意->的优先级大于&
		}
		coord& operator++()//前置++
		{
			_pnode = _pnode->_next;
			return *this;
		}
		coord& operator++(int)//后置++
		{
			coord old(*this);
			_pnode = _pnode->_next;
			return old;
		}
		coord& operator--()//前置--
		{
			_pnode = _pnode->_prev;
			return *this;
		}
		coord& operator--(int)//后置--
		{
			coord old(*this);
			_pnode = _pnode->_prev;
			return old;
		}
		bool operator!=(const coord& it)
		{
			return _pnode != it._pnode;
		}
		bool operator==(const coord& it)
		{
			return _pnode == it._pnode;
		}
	};
	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_iterator begin()const
		{
			return const_iterator(_head->_next);
		}
		const_iterator end()const
		{
			return const_iterator(_head);
		}
		iterator begin()
		{
			return iterator(_head->_next);
		}
		iterator end()
		{
			return iterator(_head);
		}
		void initialize()//创建头节点
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}
		list()
		{
			initialize();
		}
		template<class _iterator>
		list(_iterator front, _iterator later)
		{
			initialize();
			while (front != later)
			{
				push_back(*front);
				++front;
			}
		}
		list(const list<T>& it)//拷贝构造
		{
			initialize();//必须创建头节点,不然交换会传过去一个空指针导致析构出现问题
			list<T>newnode(it.begin(), it.end());
			swap(newnode);
		}
		void insert(iterator pos, const T& x)//在pos位置之前插入
		{
			node* newnode = new node(x);
			node* cur = pos._pnode;//pos位置的结点
			node* p = cur->_prev;//pos位置之前的结点
			p->_next = newnode;
			newnode->_prev = p;
			newnode->_next = cur;
			cur->_prev = newnode;
		}
		iterator erase(iterator pos)//删除pos位置的结点
		{
			assert(pos != end());
			node* cur = pos._pnode;
			node* front = cur->_prev;
			node* later = cur->_next;
			front->_next = later;
			later->_prev = front;
			delete cur;
			return iterator(later);
		}
		list<T>& operator=(list<T> p)
		{
			swap(p);
			return *this;
		}
		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 front = begin();
			while (front != end())
			{
				front = erase(front);
			}
		}
		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
	private:
		node* _head;
	};

list与vector的区别

list:
优点:
按照需要申请空间,不需要扩容。
任意位置插入删除。

缺点:
不支持下标随机访问。
cpu命中率低。(因为list是随机结构,vector是连续空间地址)

vector:
优点:
支持下标随机访问.
尾插尾删效率高。
cpu命中率高。
缺点:
前面插入删除效率低。
需要扩容。(可能导致浪费空间)

迭代器失效问题
vector插入删除都会失效,list只有删除会失效。

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

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

相关文章

第十六届中国大数据技术大会五大分论坛顺利举办!

1月8日下午&#xff0c;由苏州市人民政府指导、中国计算机学会主办、苏州市吴江区人民政府支持&#xff0c;CCF大数据专家委员会、苏州市吴江区工信局、吴江区东太湖度假区管委会、苏州市吴江区科技局、苏州大学未来科学与工程学院及DataFounain数联众创联合承办的第十六届中国…

基于java springboot+mybatis学生学科竞赛管理管理系统设计和实现

基于java springbootmybatis学生学科竞赛管理管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言…

10.Isaac教程--在Docker中通过模拟训练目标检测

在Docker中通过模拟训练目标检测 文章目录在Docker中通过模拟训练目标检测怎么运行的主机设置硬件要求软件要求NGC Docker 注册表设置第一次运行数据集生成配置您的工作区Jupyter 变量设置开始训练添加您自己的 3D 模型故障排除接下来人工智能中的一个常见问题是训练样本的数据…

02【Http、Request】

文章目录02【Http、Request】一、HTTP协议1.1 HTTP协议概述1.1.1 HTTP协议的概念1.1.2 HTTP协议的特点&#xff1a;2.1 HTTP请求的组成2.1.1 请求行2.1.2 请求头2.1.3 请求体二、HttpServletRequest对象2.1 HttpServletRequest对象简介2.2 HttpServletRequest的使用2.2.1 请求行…

Redis未授权访问漏洞(三)Redis写入反弹连接定时任务

前言 系列文章 Redis未授权访问漏洞(一)先导篇 Redis未授权访问漏洞(二)Webshell提权篇 Redis写入反弹连接任务 环境准备 攻击机&#xff1a; Centos7 IP:192.168.142.44 靶机&#xff1a;Centos7 IP:192.168.142.66 我们先来回顾一下corntab定时任务的一些基本命令&#xf…

python数据分析及可视化(二十)Power BI的可视化制作以及A股上市公司数据分析

可视化制作 通过图表展示如何用Power BI 制作可视化的图表&#xff0c;来展示可视的数据内容。 柱形图 用水平的柱子来表示不同分类数据的大小&#xff0c;类似于条形图&#xff0c;相当于竖着的条形图。堆积柱形图是不同的序列数据都堆积在一个柱子上&#xff0c;簇状柱形图…

【Spring源码】20. MergedBeanDefinitionPostProcessor修改/合并bean定义

随后进入applyMergedBeanDefinitionPostProcessors()方法applyMergedBeanDefinitionPostProcessors()方法中调用MergedBeanDefinitionPostProcessor后置处理器修改/合并bean定义 进入bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName) 从源码中可以看到&#xff0…

Spring为什么这么火 之 Spring蕴含的设计思想

目录 1、什么是IoC&#xff1f; 2、传统程序开发 3、控制反转式程序开发 4、理解Spring IoC 5、依赖注入【DI】 前言 很多人说&#xff1a;“Java程序员都是Spring程序员”&#xff0c;从这句话就能看出&#xff0c;对于Java程序员来说&#xff0c;Spring是一项必备技能&am…

Mathorcup数学建模竞赛第六届-【妈妈杯】B题:车位分布的优化设计与评价(附一等奖获奖论文、C++和matlab代码)

赛题描述 随着现代社会经济的快速发展,房地产成为国家经济发展中重要的经济增长点之一。而小区内汽车停车位的分布对于小区居民的上下班出行影响很大。请建立数学模型,解决下列问题: 问题1:分析评判小区汽车停车位分布是否合理的几个关键指标,建立评判车位分布合理的数学…

C语言常用字符串函数

专栏&#xff1a;C语言 每日一句&#xff1a;别在乎别人的目光&#xff0c;自己只管一心走路。熬过去&#xff0c;你就能看到柳暗花明&#xff0c;熬过去&#xff0c;你就能看到雨后彩虹&#xff0c;熬过去&#xff0c;你就能看到动人风景。 字符串函数前言一、求字符串长度&am…

UDS - 10.3 ECUReset (11) service

10.3 电子控制单元复位&#xff08;11&#xff09;服务 来自&#xff1a;ISO 14229-1-2020.pdf 10.3.1 服务说明 客户端使用ECUReset服务请求服务器重置。 此服务请求服务器基于ECUReset请求消息中嵌入的resetType参数值的内容有效地执行服务器重置。ECUReset肯定响应消息&am…

剑指 Offer 26树的子结构(相关话题:对称性递归,在线算法)

目录 开篇引言 题目描述 代码实现 题目拓展 拓展解读 一类 100. 相同的树 226. 翻转二叉树 104. 二叉树的最大深度 110. 平衡二叉树 543. 二叉树的直径 617. 合并二叉树 572. 另一个树的子树 965. 单值二叉树 二类 101. 对称二叉树 解题总结 开篇引言 力扣上…

Vue引入并使用Element-UI组件库的两种方式

前言 在开发的时候&#xff0c;虽然我们可以自己写css或者js甚至一些动画特效&#xff0c;但是也有很多开源的组件库帮我们写好了。我们只需要下载并引入即可。 vue和element-ui在开发中是比较般配的&#xff0c;也是我们开发中用的很多的&#xff0c;下面就介绍下如何在eue项…

【力扣/牛客刷题】二叉树篇

作者&#xff1a;✿✿ xxxflower. ✿✿ 博客主页&#xff1a;xxxflower的博客 专栏&#xff1a;【力扣、牛客刷题】篇 语录&#xff1a;⭐每一个不曾起舞的日子&#xff0c;都是对生命的辜负。⭐ 文章目录100. 相同的树572. 另一棵树的子树226. 翻转二叉树平衡二叉树101.对称二…

C++11之后的decltype类型指示符

C11之后的decltype类型指示符一、什么是decltype类型指示符二、typeid运算符三、使用decltype指示符四、decltype和引用五、decltype(auto)六、本章代码汇总一、什么是decltype类型指示符 有时会遇到这种情况&#xff1a;希望从表达式的类型推断出要定义的变量的类型&#xff…

深度对比学习综述

本文综合考察了对比学习近年的发展和进步, 提出一种新的面向对比学习的归类方法, 并基于提出的归类方法, 对现有对比研究成果进行系统综述, 并评述代表性方法的技术特点和区别, 系统对比分析现有对比学习方法在不同基准数据集上的性能表现。 摘要 在深度学习中, 如何利用大量、…

Linux - 目录与文件操作

目录1.操作目录1.1 目录切换1.2 浏览目录1.3 目录创建1.4 目录删除1.5 复制目录1.6 移动或重命名目录2. 操作文件2.1 查找文件2.2 查看文件信息2.3 查看文件内容2.4 创建文件2.5 文件修改-vim2.6 删除文件2.7 复制和重命名文件3. 文件或目录进行压缩或解压3.1 压缩3.2 解压1.操…

链路追踪工具之Zipkin

Zipkin是一个分布式跟踪系统&#xff0c;Zipkin的设计是基于谷歌的Google Dapper论文&#xff0c;它可以帮助收集时间数据&#xff0c;在microservice架构下&#xff0c;通过链路追踪&#xff0c;可以便捷的分析服务调用延迟问题。每个应用程序向Zipkin server端报告数据&#…

【高光谱、多光谱和全色图像融合】

HyperFusion: A Computational Approach for Hyperspectral, Multispectral, and Panchromatic Image Fusion &#xff08;超融合&#xff1a;高光谱、多光谱和全色图像融合的计算方法&#xff09; 高空间分辨率的高光谱图像&#xff08;HSI&#xff09;和多光谱图像&#xff…

链表热门面试题(二)

目录前言一、删除链表的倒数第 N 个结点二、两两交换链表中的节点三、旋转链表四、删除排序链表中的重复元素五、删除排序链表中的重复元素 II六、反转链表II七、删除链表中的节点八、奇偶链表前言 一、删除链表的倒数第 N 个结点 题目&#xff1a; 方法1&#xff1a;找到删除…