【C++】STL-list模拟实现

news2025/1/10 21:23:53

目录

1、本次需要实现的3个类即接口总览

2、list的模拟实现

2.1 链表结点的设置以及初始化

2.2 链表的迭代器

2.3 容量接口及默认成员函数


1、本次需要实现的3个类即接口总览

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct __List_node//创建一个T类型的链表结点
{
	__List_node(const T& data = T());//构造函数
	__List_node<T>* _next;
	__List_node<T>* _prev;
	T _data;
};
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);//构造函数。将迭代器中的结点初始化成传过来的结点

	//各种运算符重载函数
	Ref operator*();
	Ptr operator->();
	Self& operator++();
	Self operator++(int);
	Self& operator--();
	Self operator--(int);
	bool operator!=(const Self& it);
};
template<class T>
class List//真正的链表
{
public:
	typedef __List_node<T> Node;//将链表结点的名称重命名为Node
	typedef __List_iterator<T, T&, T*> iterator;
	typedef __List_iterator<T, const T&, const T*> const_iterator;
	//带头双向循环链表
	//默认成员函数
	List();
	~List();
	List(const List<T>& lt);
	List<T>& operator=(List<T> lt);

	//插入删除函数
	void clear();//clear是清除除了头节点意外的所以结点
	void push_back(const T& x);//一定要用引用,因为T不一定是内置类型
	void pop_back();
	void push_front(const T& x);
	void pop_front();
	void insert(iterator pos, const T& x);
	void erase(iterator pos);

	//迭代器相关函数
	iterator begin();
	iterator end();
	const_iterator begin() const;
	const_iterator end() const;
private:
	Node* _head;
};

2、list的模拟实现

2.1 链表结点的设置以及初始化

list是双向带头循环链表,所以链表的结点的成员变量需要有一个数值,一个指向前一个结点的指针,一个指向后一个结点的指针。初始化时,需要创建一个不存放有效值的头节点,并让头节点的两个指针都指向自己

链表的成员变量只需要一个指向头节点的指针

结点的结构体当中也需要创建一个构造函数,因为在创建结点时可以传入值

template<class T>
struct __List_node//创建一个T类型的链表结点
{
	__List_node(const T& data = T())//构造函数
		:_data(data)
		, _next(nullptr)
		, _prev(nullptr)
	{}

	__List_node<T>* _next;
	__List_node<T>* _prev;
	T _data;
};
List()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
}

上面的构造函数一定要使用引用,因为T不一定是内置类型

2.2 链表的迭代器

在上面的总览中可以看到链表的迭代器倍封装成了一个类,并且这个类有3个参数

首先,解释为什么会倍封装成一个类呢?
在vector中,迭代器就是一个指针,当我们对这个指针解引用(即*),就可以拿到这个指针所指向的数据,对其++,就可以让指针往下一个数据走,但在list中不行。如果迭代器是指向一个结点的指针,那么当对这个指针解引用时,拿到的是一个类对象,即这个结点本身,并不能拿到其中的数据,当对这个指针++时,并不能往下一个结点走所以我们需要将迭代器封装成一个类,这个类中的成员变量仍然是一个指向结点的指针,只是我们会重载一下里面的运算符,让我们*或++等操作的时候,能够直接拿到里面的数据和让指针往下一个结点。所以我们封装这个类的原因,就是为了让我们在使用list时,与使用vector等是一样的,即更加方便。实际上,迭代器这个类里面的成员变量仍然是一个指向结点的指针。

其次,解释为什么会有3个参数呢?

我们可以看到在链表类中会对迭代器进行重命名

typedef __List_iterator<T, T&, T*> iterator;
typedef __List_iterator<T, const T&, const T*> const_iterator;

因为我们对于list和const list调用的迭代器是不同的,若我们只有一个参数T,那这个时候我们重命名是没办法重命名两个的,也就是说,若只有一个参数,则需要封装两个迭代器的类,而这两个类中只有operator*和operator->是不同的,所以弄成3个参数会更好一些。

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)
	{}
	// *it
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	// ++it
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	// it++
	Self operator++(int)
	{
		Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的
		//_node = _node->_next;
		++(*this);
		return tmp;
	}
	// --it
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	// it--
	Self operator--(int)
	{
		Self tmp(*this);
		//_node = _node->_prev;
		--(*this);
		return tmp;
	}
	// it != end()
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}
};
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);
}

当我们是list调用begin是,则会调用返回值是iterator的,而iterator是__List_iterator<T, T&, T*>的,也就是调用*和->时,拿到的都是可读可写的值,反之则是只读的

int main()
{
	//对于内置类型
	List<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	lt.push_back(4);
	List<int>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << *it << " ";
		it++;
	}
	return 0;
}
class Date
{
public:
	int _year = 0;
	int _month = 1;
	int _day = 1;
};
int main()
{
	//对于自定义类型
	List<Date> lt;
	lt.push_back(Date());
	lt.push_back(Date());
	List<Date>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << it->_year << " " << it->_month << " " << it->_day << endl;
		//也可以cout << (*it)._year << " " << (*it)._month << " " << (*it)._day << endl;
		it++;
	}
	return 0;
}

->通常是在链表中存储的是自定义类型才会使用,通过上面可知->返回的是这个结构体的数值域的地址,那不应该是it->->_year吗(因为前面的it->返回后是Date*)?为了可读性,倍编译器处理了一下

这里说明一下begin和end返回的结点分别是那个

2.3 容量接口及默认成员函数

~List()
{
	clear();
	delete _head;
	_head = nullptr;
}
List(const List<T>& lt)
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	//const_iterator it = lt.begin();//这里迭代器不需要指定是那个类域,因为就是在这个类中使用
	//while (it != lt.end())
	//{
	//	push_back(*it);
	//	++it;
	//}
	for (auto e : lt)//这里与上面用迭代器一样,因为最终也会被替换成迭代器
		push_back(e);
}
/*List<T>& operator=(const List<T>& lt)
{
	if (this != &lt)
	{
		clear();
		for (ayto e : lt)
			push_back(e);
	}
	return *this;
}*/
List<T>& operator=(List<T> lt)
{
	swap(_head, lt._head);//原来的空间给这个临时变量,因为这个临时变量是自定义类型,出了作用域后会自动调用析构函数
	return *this;
}
void clear()//clear是清除除了头节点意外的所以结点
{
	iterator it = begin();
	while (it != end())
	{
		erase(it++);
	}
}
void push_back(const T& x)//一定要用引用,因为T不一定是内置类型
{
	Node* tail = _head->_prev;
	Node* newnode = new Node(x);
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;
	/*insert(end(),x);*/
}
void pop_back()
{
	Node* tail = _head->_prev;
	Node* prev = tail->_prev;
	delete tail;
	_head->_prev = prev;
	prev->_next = _head;
	//erase(--end());
}
void push_front(const T& x)
{
	Node* first = _head->_next;
	Node* newnode = new Node(x);
	_head->_next = newnode;
	newnode->_prev = _head;
	newnode->_next = first;
	first->_prev = newnode;
	//insert(begin(), x);
}
void pop_front()
{
	Node* first = _head->_next;
	Node* second = first->_next;
	delete first;
	_head->_next = second;
	second->_prev = _head;
	//erase(begin());
}
void insert(iterator pos, const T& x)
{
	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;
}
void erase(iterator pos)
{
	assert(pos != end());//不能删除头节点
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	prev->_next = next;
	next->_prev = prev;
	delete cur;
	cur = nullptr;
}
template<class T>
struct __List_node//创建一个T类型的链表结点
{
	__List_node(const T& data = T())//构造函数
		:_data(data)
		, _next(nullptr)
		, _prev(nullptr)
	{}

	__List_node<T>* _next;
	__List_node<T>* _prev;
	T _data;
};
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)
	{}
	// *it
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
	// ++it
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	// it++
	Self operator++(int)
	{
		Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的
		//_node = _node->_next;
		++(*this);
		return tmp;
	}
	// --it
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	// it--
	Self operator--(int)
	{
		Self tmp(*this);
		//_node = _node->_prev;
		--(*this);
		return tmp;
	}
	// it != end()
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}
};
template<class T>
class List//真正的链表
{
public:
	typedef __List_node<T> Node;//将链表结点的名称重命名为Node
	typedef __List_iterator<T, T&, T*> iterator;
	typedef __List_iterator<T, const T&, const T*> const_iterator;
	//带头双向循环链表
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	~List()
	{
		clear();
		delete _head;
		_head = nullptr;
	}
	List(const List<T>& lt)
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		//const_iterator it = lt.begin();//这里迭代器不需要指定是那个类域,因为就是在这个类中使用
		//while (it != lt.end())
		//{
		//	push_back(*it);
		//	++it;
		//}
		for (auto e : lt)//这里与上面用迭代器一样,因为最终也会被替换成迭代器
			push_back(e);
	}
	/*List<T>& operator=(const List<T>& lt)
	{
		if (this != &lt)
		{
			clear();
			for (ayto e : lt)
				push_back(e);
		}
		return *this;
	}*/
	List<T>& operator=(List<T> lt)
	{
		swap(_head, lt._head);//原来的空间给这个临时变量,因为这个临时变量是自定义类型,出了作用域后会自动调用析构函数
		return *this;
	}
	void clear()//clear是清除除了头节点意外的所以结点
	{
		iterator it = begin();
		while (it != end())
		{
			erase(it++);
		}
	}
	void push_back(const T& x)//一定要用引用,因为T不一定是内置类型
	{
		Node* tail = _head->_prev;
		Node* newnode = new Node(x);
		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
		/*insert(end(),x);*/
	}
	void pop_back()
	{
		Node* tail = _head->_prev;
		Node* prev = tail->_prev;
		delete tail;
		_head->_prev = prev;
		prev->_next = _head;
		//erase(--end());
	}
	void push_front(const T& x)
	{
		Node* first = _head->_next;
		Node* newnode = new Node(x);
		_head->_next = newnode;
		newnode->_prev = _head;
		newnode->_next = first;
		first->_prev = newnode;
		//insert(begin(), x);
	}
	void pop_front()
	{
		Node* first = _head->_next;
		Node* second = first->_next;
		delete first;
		_head->_next = second;
		second->_prev = _head;
		//erase(begin());
	}
	void insert(iterator pos, const T& x)
	{
		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;
	}
	void erase(iterator pos)
	{
		assert(pos != end());//不能删除头节点
		Node* cur = pos._node;
		Node* prev = cur->_prev;
		Node* next = cur->_next;
		prev->_next = next;
		next->_prev = prev;
		delete cur;
		cur = nullptr;
	}
	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;
};

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

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

相关文章

机器学习算法之KNN分类算法【附python实现代码!可运行】

一、简介 在机器学习中&#xff0c;KNN&#xff08;k-Nearest Neighbors&#xff09;分类算法是一种简单且有效的监督学习算法&#xff0c;主要用于分类问题。KNN算法的基本思想是&#xff1a;在特征空间中&#xff0c;如果一个样本在特征空间中的k个最相邻的样本中的大多数属…

每日Attention学习5——Multi-Scale Channel Attention Module

模块出处 [link] [code] [WACV 21] Attentional Feature Fusion 模块名称 Multi-Scale Channel Attention Module (MS-CAM) 模块作用 通道注意力 模块结构 模块代码 import torch import torch.nn as nnclass MS_CAM(nn.Module):def __init__(self, channels64, r4):super(…

Android NDK开发——Android Studio 3.5.2安装与配置踩坑

Android NDK开发——Android Studio 3.5.2安装与配置踩坑 一、Android Studio下载二、配置踩坑报错1&#xff1a;Failed to install the following Android SDK packages as some licences have not been accepted报错2&#xff1a;No toolchains found in the NDK toolchains …

【全开源】Java上门洗车小程序源码上门洗车APP 小程序源码支持二次开发6.0

功能特点&#xff1a; 跨界创新&#xff1a;融入科技元素&#xff0c;借助移动互联网快速发展&#xff0c;将科技引入到传统洗车业中。 科技赋能&#xff1a;具有智能化的特点&#xff0c;用户可以根据自身的需求选择不同的洗车项目和服务&#xff0c;包括洗车的时间、地点和服…

滥用 Kubernetes 资源登上月球

Sysdig 2024 年云原生安全和使用报告强调了不断变化的威胁形势&#xff0c;但更重要的是&#xff0c;随着容器和 Kubernetes 等云原生技术的采用不断增加&#xff0c;并非所有组织都遵循最佳实践。当攻击者在 Kubernetes 等操作中利用容器来利用资源时&#xff0c;这最终会给攻…

【stomp 实战】spring websocket 接收消息源码分析

后台消息的发送过程&#xff0c;我们通过spring websocket用户消息发送源码分析已经了解了。我们再来分析一下后端接收消息的过程。这个过程和后端发送消息过程有点类似。 前端发送消息 前端发送消息给服务端的示例如下&#xff1a; 发送给目的/app/echo一个消息。 //主动发…

线程安全的概念及原因

1.观察线程不安全 public class ThreadDemo {static class Counter {public int count 0;void increase() {count;}}public static void main(String[] args) throws InterruptedException {final Counter counter new Counter();Thread t1 new Thread(() -> {for (int …

ES:聚合查询语法

基础查询结构&#xff1a; GET http://ip:prot/textbook/_search { "query" : { ...query子句... }, "aggs" : { "agg_name":{ "agg_type": { "agg_arg": agg_arg_value } } }, "sort" : { ..sor…

Cesium--加载天地图

背景&#xff1a;vue-admin-temlate cesium 天地图 天地图地址&#xff1a;国家地理信息公共服务平台 天地图 步骤一&#xff1a;申请成为天地图开发者&#xff0c;创建应用 1,天地图使用方法&#xff08;点击开发资源即可看到此页面&#xff09; 2,点击控制台-登录账号 …

13:HAL---SPI

目录 一:SPL通信 1:简历 2:硬件电路 3:移动数据图 4:SPI时序基本单元 A : 开/ 终条件 B:SPI时序基本单元 A:模式0 B:模式1 C:模式2 D:模式3 C:SPl时序 A:发送指令 B: 指定地址写 C:指定地址读 5&#xff1a;NSS(CS) 6&#xff1a;时钟 二: W25Q64 1:简历 2…

Star-CCM+通过将所有部件创建一个区域的方式分配至区域后子区域的分离,子区域材料属性的赋值,以及物理连续体的创建方法介绍

前言 上次介绍了将零部件分配至区域的方法与各个方法之间的区别&#xff0c;本文将继续上次的讲解&#xff0c;将其中的“将所有部件分配至一个区域”的应用进行补充。 如下图所示&#xff0c;按照将所有部件创建一个区域的方式分配至区域后&#xff0c;在区域下就会有一个区域…

springboot+vue实现登录注册,短信注册以及微信扫描登录

说明&#xff1a;微信扫描登录需要微信注册--要钱&#xff0c;感谢尚硅谷提供的免费接口&#xff1b;短信注册需要阿里云的注册很麻烦并且短信费&#xff0c;没有接口&#xff0c;所以不打算实现&#xff0c;不过能做出效果。 目录 一、建立数据库 二、后端idea实现接口 1.…

全球首发:抗量子、以太坊兼容测试网正式上线

量子计算机将有能力破解目前互联网上使用的主要加密算法&#xff0c;影响的领域包括银行应用程序、电子邮件服务和社交媒体平台。 2023年5月7日&#xff0c;QANplatform推出了全球首个兼容以太坊的抗量子区块链测试网&#xff0c;此举将使开发者能够使用任何编程语言来编写智能…

thinkphp6使用layui分页组件做分页效果

博主用的是layui2.9.8的版本&#xff0c;但这个版本的分页组件是动态效果的&#xff0c;但我需要的是静态分页&#xff0c;所以我自己封装了一个生成layui的分页代码生成代码。代码如下&#xff1a; 1、先创建文件&#xff0c;路径是extent/layui/LayuiPage.php&#xff0c;加…

Java实战:验证改进的哥德巴赫猜想

改进的哥德巴赫猜想&#xff08;Improved Goldbach’s Conjecture&#xff09;声称每个大于5的奇数都可以表示为三个素数之和。这个猜想是对原始哥德巴赫猜想的扩展&#xff0c;针对奇数的情况。原始哥德巴赫猜想是指每个大于2的偶数都可以表示为两个素数之和。尽管改进的哥德巴…

ROS 2边学边练(45)-- 构建一个能动的机器人模型

前言 在上篇中我们搭建了一个机器人模型(其由各个关节&#xff08;joint&#xff09;和连杆&#xff08;link&#xff09;组成)&#xff0c;此篇我们会通过设置关节类型来实现机器人的活动。 在ROS中&#xff0c;关节一般有无限旋转&#xff08;continuous&#xff09;,有限旋转…

el-dialog设置el-head固定

0 效果 1 代码 ::v-deep .adTextDetailDialogClass .el-dialog__body{max-height: calc(100vh - 150px);overflow: auto;border-top:1px solid #dfdfdf;border-bottom:1px solid #dfdfdf; } ::v-deep .adTextDetailDialogClass .el-dialog{position: fixed;height:fit-content;…

15-LINUX--线程的创建与同步

一.线程 1.线程的概念 线程是进程内部的一条执行序列或执行路径&#xff0c;一个进程可以包含多条线程。 2.线程的三种实现方式 ◼ 内核级线程&#xff1a;由内核创建&#xff0c;创建开销大&#xff0c;内核能感知到线程的存在 ◼ 用户级线程&#xff1a;线程的创建有用户空…

springboot 引入第三方bean

如何进行第三方bean的定义 参数进行自动装配

数据库中索引的底层原理和SQL优化

文章目录 关于索引B 树的特点MySQL 为什么使用 B 树&#xff1f; 索引分类聚簇索引 和 非聚簇索引覆盖索引索引的最左匹配原则索引与NULL索引的代价大表结构修改 SQL优化EXPLAIN命令选择索引列其它细节 关于索引 索引是一种用来加快查找效率的数据结构&#xff0c;可以简单粗暴…