STL:List从0到1

news2024/11/19 8:52:15

请添加图片描述

在这里插入图片描述

🎉个人名片

🐼作者简介:一名乐于分享在学习道路上收获的大二在校生
🙈个人主页🎉:GOTXX
🐼个人WeChat:ILXOXVJE
🐼本文由GOTXX原创,首发CSDN🎉🎉🎉
🐵系列专栏:零基础学习C语言----- 数据结构的学习之路----C++的学习之路
🐓每日一句:如果没有特别幸运,那就请特别努力!🎉🎉🎉
————————————————

🎉文章简介

🎉本篇文章将 介绍如何使用C++编写代码来实现一个类似于STL中的List容器 相关知识进行分享!
💕如果您觉得文章不错,期待你的一键三连哦,你的鼓励是我创作动力的源泉,让我们一起加 油,一起奔跑,让我们顶峰相见!!!🎉🎉🎉
——————————————————

一.前言

这篇文章将介绍如何使用C++编写代码来实现一个类似于STL中的List容器。 list是一个可以在常数范围内在任意位置进行插入和删除的序列式容器。在这篇文章中,你将学习并实现List的常见功能,如添加元素、删除元素等。通过实现自己的List容器,你将更好地熟悉List的使用及相关特性,并提升对C++语言的理解和掌握。
————————————————

二.List的介绍

List文档介绍链接: link

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底层是一个带头双向循环链表

如图:
在这里插入图片描述
在这里插入图片描述
库里面的begin() 与end() 返回的节点位置:

begin()返回的是头节点的下一个节点;
而end()返回的是头节点;

二.List的模拟实现

重点::迭代器的实现

1. 构造函数
template<class T>
struct ListNode
{
	ListNode<T>(const T& x=T())    
		:_next(nullptr)
		,_prev(nullptr)
		,_val(x)
	{
	}
	ListNode<T>* _next;
	ListNode<T>* _prev;
	T _val;
};

库里面的List类的构造函数是另写一个函数,因为这个函数拷贝构造会使用,然后复用它,所以我们也这样实现;

template<class T>
class List
{
public:
	void empty_List()
	{
		_phead = new node;
		_phead->_next = _phead;
		_phead->_prev = _phead;
	}
	List()
	{
		empty_List();
	}
}
2. 拷贝构造函数
List(const List<T>& lt)
{
	empty_List();     //初始化
	for (const auto& e : lt)    
	{
		push_back(e);        //将lt里面的数据依次尾插
	}
}
3. 插入函数

思路:记录前一个和后一个节点,然后连接

在这里插入图片描述

iterator insert(iterator pos, const T& x)
{
	node* newnode = new node(x);    //构造一个节点
	node* next = pos._node;         
	node* prev = next->_prev;      //记录前一个
	newnode->_next = next;         
	next->_prev = newnode;         //链接
	newnode->_prev = prev;
	prev->_next = newnode;

	return pos;
}
4. 尾插函数

复用insert函数

void push_back(const T& x)
{
/*	node* newnode = new node(x);
	node* tail = _phead->_prev;
	tail->_next = newnode;
	newnode->_prev = tail;       //不复用的写法
	newnode->_next = _phead;
	_phead->_prev = newnode;*/

	insert(end(), x);
}
5. 头插函数

复用insert函数

	void push_front(const T& x)
	{
		insert(begin(),x);
	}
6. 删除函数
iterator erase(iterator pos)
{
	assert(pos!=end());

	node* prev = pos._node->_prev;     //保存前一个节点
	node* next = pos._node->_next;     //保存后一个节点
	prev->_next = next;               //连接
	next->_prev = prev;
	delete pos._node;                  //释放掉该节点

	return next;                       //返回删除元素的下一个节点
}
7. 尾删函数

复用删除函数

	void pop_back()
	{
		//erase(end()._node->_prev);
		erase(--end());       //头节点的前指针指向的是最后一个节点

	}
	
8. 头删函数

复用删除函数

	void pop_front()
	{
		erase(begin());     
	}
9. 迭代器的实现

因为链表的底层物理空间不是连续的,所以不能使用原生指针类实现。因为原生指针++,可以找到下一个数据,但是链表的节点与节点之间不是连续的,指针++,不能找到下一个节点,所以我们需要操作符重载,改变 ++, != ,* 等操作符的行为;又因为节点指针是内置类型,不能进行操作符重载,所以我们只能将它进行封装,封装在一个类里面,进行重载;

template<class T>
struct __List_iterator       
{
	typedef ListNode<T> node;
	typedef __List_iterator<T> self;

	__List_iterator(node* node)       //构造函数
		:_node(node)
	{}
	self& operator++()               //运算符的重载
	{
		_node = _node->_next;       //前置++,返回++后的值
		return *this;
	}
	self& operator++(int)
	{
		self tmp(_node);         //保存++前的值
		_node = _node->_next;

		return tmp;         //返回++前的值
	}
	self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	self& operator--(int)
	{
		self tmp(_node);
		_node = _node->_prev;

		return tmp;
	}
	T& operator*()
	{
		return _node->_val;
	}
	bool operator!=(const self& s)
	{
		return s._node!= this->_node;
	}
	bool operator==(const self& s)
	{
		return s._node == this->_node;
	}
	node* _node;
};
10. 赋值运算符重载

传统写法:

	void clear()
	{
		iterator lt = begin();
		while (lt != end())
		{
			lt = erase(lt);
		}
	}
//lt1=lt2
List<T>& operator=(const List<T>& lt)
{
	clear();                                 //清空函数,将链表中的有效数据删除掉,保留头节点
	for (const auto& e : lt)
	{
		push_back(e);            //依次尾插
	}

	return *this;
}

现代写法:

void swap(List<T>& lt)
{
	std::swap(_phead, lt->_phead);
}
//lt1=lt2
List<T>& operator=(List<T> lt)    //lt是lt2的拷贝构造
{
	swap(lt);      //交换lt与lt1

	return *this;    //返回
}
补充知识:

typedef 放在类里面与外面的区别:
如果是放在公有里面,则类外面也可以使用,但是要指定类域;
如果是私有的话,则类外面不能使用;

三.总代码:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace L
{
	template<class T>
	struct ListNode
	{
		ListNode<T>(const T& x=T())
			:_next(nullptr)
			,_prev(nullptr)
			,_val(x)
		{
		}
		ListNode<T>* _next;
		ListNode<T>* _prev;
		T _val;
	};

	template<class T>
	struct __List_iterator
	{
		typedef ListNode<T> node;
		typedef __List_iterator<T> self;

		__List_iterator(node* node)
			:_node(node)
		{}
		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}
		self& operator++(int)
		{
			self tmp(_node);
			_node = _node->_next;

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

			return tmp;
		}
		T& operator*()
		{
			return _node->_val;
		}
		bool operator!=(const self& s)
		{
			return s._node!= this->_node;
		}
		bool operator==(const self& s)
		{
			return s._node == this->_node;
		}
		node* _node;
	};

	template<class T>
	class List
	{
	public:
		typedef ListNode<T> node;
		typedef __List_iterator<T>  iterator;

		iterator begin()
		{
			return iterator(_phead->_next);
		}
		iterator end()
		{
			return iterator(_phead);
		}

		void empty_List()
		{
			_phead = new node;
			_phead->_next = _phead;
			_phead->_prev = _phead;
		}
		List()
		{
			empty_List();
		}
		List(const List<T>& lt)
		{
			empty_List();

			for (const auto& e : lt)    //引用更好,如果T类型是自定义类型的话
			{
				push_back(e);
			}
		}
		//lt1=lt2
		List<T>& operator=(const List<T>& lt)
		{
			clear();
			for (const auto& e : lt)
			{
				push_back(e);
			}

			return *this;
		}
		void swap(List<T>& lt)
		{
			std::swap(_phead, lt->_phead);
		}
		//lt1=lt2
		List<T>& operator=(List<T> lt)
		{
			swap(lt);

			return *this;
		}

		void clear()
		{
			iterator lt = begin();
			while (lt != end())
			{
				lt = erase(lt);
			}
		}
		~List()
		{
			clear();
			delete _phead;
			_phead = nullptr;
		}


		void push_back(const T& x)
		{
		/*	node* newnode = new node(x);
			node* tail = _phead->_prev;
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _phead;
			_phead->_prev = newnode;*/

			insert(end(), x);
		}
		void push_front(const T& x)
		{
			insert(begin(),x);
		}
		iterator insert(iterator pos, const T& x)
		{
			node* newnode = new node(x);
			node* next = pos._node;
			node* prev = next->_prev;
			newnode->_next = next;
			next->_prev = newnode;
			newnode->_prev = prev;
			prev->_next = newnode;

			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(pos!=end());

			node* prev = pos._node->_prev;
			node* next = pos._node->_next;
			prev->_next = next;
			next->_prev = prev;
			delete pos._node;

			return next;
		}
		void pop_back()
		{
			//erase(end()._node->_prev);
			erase(--end());

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

	private:
		node* _phead;
	};

请添加图片描述

0

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

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

相关文章

IO流(5)——转换流

不同编码读取出现乱码的问题 解决方法 字符输入转换流&#xff08;InputStreamReader&#xff09;

pytorch 入门基础知识(Pytorch 01)

一 深度学习基础相关 深度学习三个主要的方向&#xff1a;计算机视觉&#xff0c;自然语言&#xff0c;语音识别。 机器学习核心组件&#xff1a;1 数据集(data)&#xff0c;2 前向传播的model(net)&#xff0c;3 目标函数(loss)&#xff0c; 4 调整模型参数和优化函数的算法…

【Linux-网络编程】

Linux-网络编程 ■ 网络结构■ C/S结构■ B/S结构 ■ 网络模型■ OSI七层模型■ TCP/IP四层模型 ■ TCP■ TCP通信流程■ TCP三次握手■ TCP四次挥手 ■ 套接字&#xff1a;socket 主机IP 主机上的进程&#xff08;端口号&#xff09;■ TCP传输文件 ■ 网络结构 ■ C/S结构…

Java学习记录(二十二)反射,动态代理

反射获取class对象的三种方式 1、Class.forName(全类名) 2、类名.class 3、对象.getClass() 实现代码如下&#xff1a; package com.itazhang.Demo1;public class MyReflectDemo1 {public static void main(String[] args) throws ClassNotFoundException {//第一种获取cl…

Nacos源码流程图

1.Nacos1.x版本服务注册与发现源码 流程图地址&#xff1a;https://www.processon.com/view/link/634695eb260d7157a7bc6adb 2.Nacos2.x版本服务注册与发现源码 流程图地址&#xff1a;https://www.processon.com/view/link/634695fb260d7157a7bc6ae0 3.Nacos2.x版本GRPC…

【Linux进程信号】信号的发送与保存

【Linux进程信号】信号的发送与保存 目录 【Linux进程信号】信号的发送与保存阻塞信号1. 信号其他相关常见概念2. 在内核中的表示3. sigset_t4. 信号集操作函数sigprocmasksigpendingsignal测试这几个系统调用接口 进程地址空间第三讲捕捉信号1. 内核如何实现信号的捕捉2. siga…

Jenkins 面试题及答案整理,最新面试题

Jenkins中如何实现持续集成与持续部署&#xff1f; Jenkins通过自动化构建、测试和部署应用程序来实现持续集成与持续部署&#xff08;CI/CD&#xff09;。这个过程包括以下步骤&#xff1a; 1、源代码管理&#xff1a; Jenkins支持与多种版本控制系统集成&#xff0c;如Git、…

Java项目:55 springboot基于SpringBoot的在线视频教育平台的设计与实现015

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 在线视频教育平台分为管理员和用户、教师三个角色的权限模块。 管理员所能使用的功能主要有&#xff1a;首页、个人中心、用户管理、教师管理、课程信…

绝赞春招拯救计划 -- 操作系统,组成原理,计网

进程和线程 进程 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间&#xff0c;一个进程可以有多个线程 线程 进程中的一个执行任务&#xff08;控制单元&#xff09;&#xff0c;负责当前进程中程序的执行。一个进程至少有一个线程&#xff0c;一个进程可以…

关于Apache

文章目录 一、httpd 安装组成1、常见http 服务器程序2、apache 介绍和特点2.1 关于apache2.1.1 apache 功能2.1.2 apache特性 2.2 MPM multi-processing module 工作模式2.2.1 prefork2.2.2 worker2.2.3 event 3、Httpd 安装和相关文件3.1 包安装httpd并启动httpd服务3.1.1 版本…

配置vscode环境极简版(C/C++)(图文)

前言 众所周知&#xff0c;vscode是一个代码编辑器&#xff0c;不能直接编译运行我们敲的代码&#xff0c;必须提前配置好环境&#xff0c;而这也是劝退一众小白的一大重要因素&#xff0c;下面我想以一种提纲挈领的方式带大家走一遍从配置环境到运行实操代码的全过程。 安装…

用户故事到需求实例化

用户故事 用户故事是敏捷开发方法中的核心概念之一&#xff0c;它提供了一种简洁的方式来描述软件功能需求&#xff0c;同时强调这些功能为用户或业务带来的价值。用户故事通常是由用户、产品经理或业务分析师编写的简短描述&#xff0c;用于与开发团队沟通需求&#xff0c;并…

面向对象(下)

目录 01、static1.1、static的使用1.2、static应用举例1.3、单例(Singleton)设计模式 02、main方法的语法03、类的成员之四&#xff1a;代码块04、关键字&#xff1a;final05、抽象类与抽象方法5.1、多态的应用&#xff1a;模板方法设计模式(TemplateMethod) 06、接口(interfac…

flink1.18.0 自定义函数 接收row类型的参数

比如sql中某字段类型 array<row<f1 string,f2 string,f3 string,f4 bigint>> 现在需要编写 tableFunction 需要接受的参数如上 解决方案 用户定义函数|阿帕奇弗林克 --- User-defined Functions | Apache Flink

C语言数据结构(7)——树、二叉树前言

欢迎来到博主的专栏——C语言数据结构 博主ID&#xff1a;代码小豪 文章目录 树二叉树特殊二叉树满二叉树完全二叉树 完全二叉树的存储结构 树 树是一个非线性的数据结构&#xff0c;由N个结点构成的集合。 树的各个结点由一个根结点联系起来&#xff0c;这个根节点没有前驱…

小球垂直跳动,C语言模拟重力加速度

位移公式 1、速度和时间关系&#xff1a; 2、位移和时间关系&#xff1a; 3、力和加速度关系&#xff1a; 4、空气阻力&#xff1a; 受理分析 以向下运动为正方向 1、向下运动整体受力&#xff0c;重力加空气阻力: 2、向上运动整理受力&#xff0c;重力减空气阻力&…

Apache zookeeper kafka 开启SASL安全认证

背景&#xff1a;我之前安装的kafka没有开启安全鉴权&#xff0c;在没有任何凭证的情况下都可以访问kafka。搜了一圈资料&#xff0c;发现有关于sasl、acl相关的&#xff0c;准备试试。 简介 Kafka是一个高吞吐量、分布式的发布-订阅消息系统。Kafka核心模块使用Scala语言开发…

MySQL基础架构

文章目录 MySQL基础架构一、连接器 - 建立连接&#xff0c;权限认证二、查缓存 - 提高效率三、分析器 - 做什么四、优化器 - 怎么做五、执行器 - 执行语句六、存储引擎1、存储引擎的概述2、存储引擎的对比3、存储引擎的命令4、存储引擎的选择 MySQL基础架构 大体来说&#xff…

力扣-20. 有效的括号(回顾知识哈希表,栈)

给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有…

HTML万字学习总结

html文本标签特殊符号图片音频与视频超链接表单列表表格语义标签(布局) html文本标签 标签简介<html></html>根目录<head></head>规定文档相关的配置信息&#xff08;元数据<body></body>元素表示文档的内容<meta></meta>表示…