【C++笔记】C++ list类模拟实现

news2024/12/23 2:50:01

【C++笔记】C++ list类模拟实现

  • 一、初始化和各种构造
    • 1.1、准备工作
    • 1.2、各种构造和析构
  • 二、插入和删除
    • 2.1、插入
    • 2.2、删除
  • 三、迭代器
    • 3.1、正向迭代器
    • 3.2、反向迭代器
    • 3.3、提供迭代器位置
  • 四、其他一些接口
    • 4.1、链表的长度和判空
    • 4.2、返回链表的头尾结点

一、初始化和各种构造

C++的list类使用的模型是带头双向循环链表:
在这里插入图片描述
所以今天我们也是基于这个模型来实现一个list。

1.1、准备工作

在定义链表类之前,我们先要定义链表的节点类:

// list节点类
template <class T>
struct ListNode {
	// 构造函数
	ListNode(const T& val = T()) {
		_val = val;
		_pre = nullptr;
		_next = nullptr;
	}
	T _val;
	ListNode<T>* _pre; // 指向前一个节点
	ListNode<T>* _next; // 指向后一个节点
};

然后就是list类了:

template <class T>
class list {
	typedef ListNode<T> Node;
public :
private :
	Node* _head;
	size_t _size;
};

因为我们所写的是带头链表,所以在list类里就只需要顶一个头节点和size即可。

1.2、各种构造和析构

有了链表类,我们就要先来实现各种构造和析构了。
空初始化
但在开始实现构造之前,我们必须先得实现一个“空初始化”,因为我们设计的链表是带头结点的,并且这个头结点是不算入有效数据的。
所以我们要先将这个头结点申请出来,这也就是空初始化要做的事情:

// 空初始化
void emptyInit() {
	// 开辟一个头节点
	Node* newHead = new Node();
	_head = newHead;
	_head->_next = _head;
	_head->_pre = _head;
	_size = 0;
}

无参构造
无参构造的作用就是只创建一个链表对象,其他什么都不做,所以我们直接调用空初始化即可:

// 无参构造函数
list() {
	emptyInit();
}

以n个相同值构造
这个其实我们可以直接复用后面写的尾插,直接一直追加即可。
在正式插入之前,我们还是先要处理头节点,即调用空初始化:

// 以n个相同值构造
list(int n, const T& val = T()) {
	// 先空初始化
	emptyInit();
	//  插入
	for (int i = 0; i < n; i++) {
		push_back(val);
	}
}

以一段迭代器区间初构造
这个其实也和其他容器是一样的:

// 以一段迭代器区间初始化
template <class Iterator>
list(Iterator first, Iterator last) {
	// 先空初始化
	emptyInit();
	while (first != last) {
		push_back(*first);
		first++;
	}
}

拷贝构造
对于其他容器而言,链表的拷贝构造我想是最简单的了,因为它的空间本身就不连续,也就不必申请新的一段连续的空间再拷贝数据了。
其实我们直接将两个链表的哨兵位节点交换即可。
所以我们可以先实现一个交换函数:

// 交换
void Swap(list<T>& lt) {
	swap(_head, lt._head);
	swap(_size, lt._size);
}

然后我们在构造函数中创建一个临时对象,再将这个对象与我们要构造的对象交换即可:

// 拷贝构造
list(const list<T>& lt) {
	// 先空初始化
	emptyInit();

	// 创建一个临时对象
	list<T> temp(lt.begin(), lt.end());
	// 交换即可
	Swap(temp);
}

析构函数
析构函数,我这里并不想使用以前的方法依次删除节点,我这里打算复用其他的函数。
我们先得要实现一个清数据的函数——只清理有效数据,不清理哨兵头节点:

// 清除数据
void clear() {
	iterator it = begin();
	while (it != end()) {
		it = erase(it);
	}
}

这里用到的迭代器后面后讲到。

然后在析构函数中,我们只需要调用一次clear然后再将哨兵头节点释放掉即可:

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

二、插入和删除

2.1、插入

尾插
尾插其实就和我们以前写的带头双向循环链表一模一样了:

	// 尾插
void push_back(const T& val) {
	Node* tail = _head->_pre;
	// 申请一个新节点
	Node* newNode = new Node(val);
	tail->_next = newNode;
	newNode->_pre = tail;
	newNode->_next = _head;
	_head->_pre = newNode;
	_size++;
}

头插

	// 头插
	void push_front(const T& val) {
		// 申请新的头节点
		Node* newHead = new Node(val);
		// 原来的头节点
		Node* head = _head->_next;
		_head->_next = newHead;
		newHead->_pre = _head;
		newHead->_next = head;
		head->_pre = newHead;
		_size++;
	}

随机插入
在pos位置之前插入一个新节点,并返回新节点的位置:

iterator insert(iterator pos, const T& val) {
	// 申请一个新节点
	Node* newNode = new Node(val);
	Node* Pre = pos._node->_pre;
	Node* Next = pos._node;
	Pre->_next = newNode;
	newNode->_pre = Pre;
	newNode->_next = Next;
	Next->_pre = newNode;
	_size++;
	return newNode;
}

2.2、删除

尾删

// 尾删
void pop_back() {
	assert(!empty());
	Node* tail = _head->_pre;
	Node* Pre = tail->_pre;
	// 释放尾节点
	delete tail;
	Pre->_next = _head;
	_head->_pre = Pre;
	_size--;
}

头删

// 头删
void pop_front() {
	assert(!empty());
	// 旧的头节点
	Node* head = _head->_next;
	// 新的头节点
	Node* newHead = head->_next;
	// 释放旧的头节点
	delete head;
	_head->_next = newHead;
	newHead->_pre = _head;
	_size--;
}

随机删除
删除pos位置的节点,并返回被删除节点的下一个节点的位置:

iterator erase(iterator pos) {
	assert(!empty());
	Node* cur = pos._node;
	Node* Pre = cur->_pre;
	Node* Next = cur->_next;
	// 释放原来的节点
	delete cur;
	Pre->_next = Next;
	Next->_pre = Pre;
	_size--;
	return Next;
}

三、迭代器

因为链表的哨兵头节点是私有的,并且链表也不支持随机访问(不能实现方括号重载),所以我们遍历链表的方式就只剩一种——迭代器。

3.1、正向迭代器

因为链表的空间不是连续的,所以我们就不能直接用原生指针来模拟了。我们需要对原生指针再进行封装。
并且迭代器的++和–也要通过运算符重载的方式达到:

// list迭代器类
template <class T, class ref, class ptr>
struct ListIterator {
	typedef ListNode<T> Node;
	typedef ListIterator<T, ref, ptr> iterator;
public :
	// 构造函数
	ListIterator(Node* node = nullptr) {
		_node = node;
	}

	// 拷贝构造
	ListIterator(const iterator& it) {
		_node = it._node;
	}

	// 星号解引用运算符重载
	ref operator*() {
		return _node->_val;
	}

	// 箭头解引用运算符重载
	ptr operator->() {
		return &_node->_val;
	}
	// 前置++运算符重载
	iterator& operator++() {
		_node = _node->_next;
		return *this;
	}

	// 后置++运算符重载
	iterator& operator++(int) {
		iterator* temp = new iterator(this->_node);
		++(*this);
		return *temp;
	}

	

	// 前置--运算符重载
	iterator& operator--() {
		_node = _node->_pre;
		return *this;
	}

	// 后置--运算符重载
	iterator& operator--(int) {
		iterator* temp = new iterator(this->_node);
		--(*this);
		return *temp;
	}

	// 等于运算符重载
	bool operator==(const iterator& it) {
		return _node == it._node;
	}

	// 不等于运算符重载
	bool operator!=(const iterator& it) {
		return !((*this) == it);
	}
public :
	Node* _node;
};

3.2、反向迭代器

实现反向迭代器我们其实只需要适配正向迭代器即可,因为反向迭代器的逻辑几乎和正向迭代器是一样的,只是在对反向迭代器进行++和–的时候需要和正向迭代器相反:

// list反向迭代器类
template <class Iterator, class Ref, class Ptr>
struct Reverse_iterator
{
	Iterator _it;
	typedef Reverse_iterator<Itertor, Ref, Ptr> Self;

	Reverse_iterator(Iterator it)
		: _it(it)
	{}

	Ref operator*()
	{
		Iterator tmp = _it;
		return *(--tmp);
	}

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

	Self& operator++()
	{
		--_it;
		return *this;
	}

	Self& operator--()
	{
		++_it;
		return *this;
	}

	bool operator!=(const Self& s)
	{
		return _it != s._it;
	}
};

3.3、提供迭代器位置

// 正向const迭代器起始位置
const_iterator begin() const
{
	return const_iterator(_head->_next);
}
// 正向const迭代器结束位置
const_iterator end() const
{
	return const_iterator(_head);
}
 // 正向迭代器起始位置
iterator begin()
{
	return iterator(_head->_next);
}
 // 正向迭代器结束位置
iterator end()
{
	return iterator(_head);
}
 // 反向const迭代器起始位置
const_reverse_iterator rbegin() const
{
	return const_reverse_iterator(end());
}
 // 反向const迭代器结束位置
const_reverse_iterator rend() const
{
	return const_reverse_iterator(begin());
}
 // 反向迭代器起始位置
reverse_iterator rbegin()
{
	return reverse_iterator(end());
}
 // 反向迭代器结束位置
reverse_iterator rend()
{
	return reverse_iterator(begin());
}

四、其他一些接口

4.1、链表的长度和判空

// 返回长度
	size_t size() {
		return _size;
	}
	// 判断是否为空
	bool empty() {
		return _size == 0;
	}

4.2、返回链表的头尾结点

// 返回链表的头一个节点(第一个有效节点的值)
	T& front() {
		assert(!empty());
		return _head->_next->_val;
	}

	// const版本的头节点
	const T& front() const {
		assert(!empty());
		return _head->_next->_val;
	}

	// 返回链表的尾节点(最后一个有效节点的值)
	T& back() {
		assert(!empty());
		return _head->_pre->_val;
	}

	// const版本尾节点
	const T& back() const {
		assert(!empty());
		return _head->_pre->_val;
	}

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

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

相关文章

面试题 17.08. 马戏团人塔

题目链接 面试题 17.08. 马戏团人塔 mid 题目描述 有个马戏团正在设计叠罗汉的表演节目&#xff0c;一个人要站在另一人的肩膀上。出于实际和美观的考虑&#xff0c;在上面的人要比下面的人矮一点且轻一点。已知马戏团每个人的身高和体重&#xff0c;请编写代码计算叠罗汉最多…

Microsoft 网络监控

随着网络的发展和变得越来越复杂&#xff0c;公司比以往任何时候都更需要监控其网络基础设施&#xff0c;因为即使是轻微的系统中断也可能导致重大损失。网络监控工具提供实时数据和网络状态的图形概述。这使您能够准确地了解正在发生的事情&#xff0c;以便您知道需要更改的位…

进程间的通信方式

文章目录 1.简单介绍2.管道2.1管道的基础概念**管道读写规则**:**管道特点** 2.2匿名管道匿名管道父子进程间通信的经典案例&#xff1a; 2.3命名管道基本概念:命名管道的创建&#xff1a;命名管道的打开规则&#xff1a;匿名管道与普通管道的区别**例子&#xff1a;用命名管道…

基于SpringBoot+Vue的宠物领养饲养交流管理平台设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

HTML实现移动端布局与页面自适应

我们所说的布局方式&#xff0c;这里我们通常指的是width和height在不同页面情况下面的改变。 常见页面的布局方式有 静态布局 &#xff08;px布局&#xff0c;就是固定其高宽&#xff0c;不论页面怎样放大缩小&#xff0c;其占领的依旧是&#xff0c;使用px固定了的高宽&…

这种方法可以解决开发中的重复“造轮子”

一、前言 开发中&#xff0c;一直听到有人讨论是否需要重复造轮子&#xff0c;我觉得有能力的人&#xff0c;轮子得造。但是往往开发周期短&#xff0c;用轮子所节省的时间去更好的理解业务&#xff0c;应用到业务中&#xff0c;也能清晰发现轮子的利弊&#xff0c;一定意义上解…

PyTorch深度学习(三)【Logistic Regression、处理多维特征的输入】

Logistic Regression 这个名字叫做回归&#xff0c;做的是分类。 线性和logistic的模型&#xff1a; 使用的损失函数&#xff1a;二分类交叉熵 &#xff08;这个也叫做BCELoss&#xff09; logistic要做的事&#xff1a; 代码&#xff1a; import torch# import torch.nn.fun…

Java基于SpringBoot的校园疫情防控系统

文章目录 第一章2.主要技术第三章第四章 系统设计4.1功能结构4.2 数据库设计4.2.1 数据库E/R图4.2.2 数据库表 第五章 系统功能实现5.1系统功能模块5.2后台功能模块5.2.1管理员功能 源码咨询 第一章 springboot校园疫情防控系统演示录像2022 一个好的系统能将校园疫情防控的管理…

VB求平均值

VB求平均值 Private Function pj(x() As Integer) As SingleDim m%, n%, i%, s%m LBound(x): n UBound(x)For i m To ns s x(i)Next ipj s / (n - m 1) End Function Private Sub Command1_Click()Dim a%(1 To 10), i%, aver!For i 1 To 10a(i) Int(Rnd() * 10) 随机…

IMX6ULL移植篇-Linux内核编译

一. Linux内核 Linux 官网为 https://www.kernel.org &#xff0c;所以你想获取最新的 Linux 版本就可以在这个网站上下载。 Linux-4.x 版本 的 Linux 和 5.x 版本没有本质上的区别&#xff0c; 5.x 更多的是加入了一些新的平台、新的外设驱动而已。 NXP 会从网址 …

提升科研可复现性:和鲸聚焦 AI for Science 全生命周期管理

今年三月&#xff0c;国家科技部会同自然科学基金委正式启动“人工智能驱动的科学研究&#xff08;AI for Science&#xff09;”专项部署工作。数据驱动的科学研究长期以来面临诸多困境&#xff0c;针对传统科研工作流中过度依赖人类专家经验与体力的局限性&#xff0c;AI4S 旨…

优化软件系统,解决死锁问题,提升稳定性与性能 redis排队下单

项目背景&#xff1a; 随着用户数量的不断增加&#xff0c;我们的速卖通小管家软件系统面临了一个日益严重的问题&#xff1a;在从存储区提供程序的数据读取器中进行读取时&#xff0c;频繁出现错误。系统报告了一个内部异常: 异常信息如下&#xff1a; 从存储区提供程序的数…

nvme各模块间的关系总结

目录&#xff1a;driver/host/nvme/makefile # SPDX-License-Identifier: GPL-2.0 ccflags-y -I$(src)obj-$(CONFIG_NVME_CORE) nvme-core.o obj-$(CONFIG_BLK_DEV_NVME) nvme.o obj-$(CONFIG_NVME_FABRICS) nvme-fabrics.o obj-$(CONFIG_NVME_RDMA) nvme-rdma.…

02、Servlet核心技术(下)

目录 1 ServletJDBC应用&#xff08;重点&#xff09; 2 重定向和转发&#xff08;重点&#xff09; 2.1 重定向的概述 2.2 转发的概述 3 Servlet线程安全&#xff08;重点&#xff09; 4 状态管理&#xff08;重点 &#xff09; 5 Cookie技术&#xff08;重点&#xf…

26 环形链表II

环形链表 II 题解1 哈希表题解2 双指针 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表…

pgzrun 拼图游戏制作过程详解(10)

10. 拼图游戏继续升级——多关卡拼图 初始化列表Photos用来储存拼图文件名&#xff0c;Photo_ID用来统计当下是第几张拼图&#xff0c;Squares储存当下拼图的24张小拼图的文件名&#xff0c;Gird储存当下窗口上显示的24个小拼图及坐标。 Photos["girl_","boy_…

“顽固”——C语言用栈实现队列

解题图解&#xff1a; 1、 先用stack1存储push来的数据 2、每当要pop数据时&#xff0c;从stack2中取&#xff0c;如果 stack2为空&#xff0c;就先从stack1中“倒”数据到stack2。 这就是用栈实现队列的基本操作 这道题看起来比较容易&#xff0c;但是&#xff01;如果你用C语…

jupyter notebook插件安装及插件推荐

安装插件 安装插件选择的工具栏 pip install jupyter_contrib_nbextensions将插件工具栏添加到jupyter notebook页面 jupyter contrib nbextension installdisable configuration for nbextensions without explicit compatibility (they may break your notebook environme…

《Kubernetes部署篇:Ubuntu20.04基于containerd部署kubernetes1.25.14集群(多主多从)》

一、架构图 如下图所示: 二、环境信息 1、资源下载基于containerd部署容器版kubernetes1.25.14集群资源合集 2、部署规划主机名K8S版本系统版本内核版本IP地址备注k8s-master-121.25.14Ubuntu 20.04.5 LTS5.15.0-69-generic192.168.1.12master节点 + etcd节点k8s-master-131.…

【超实用】2023年,学生上班族如何简单快速,低成本的搭建一个博客网站

文章目录 前言实操环节香港虚拟机购买博客搭建ssl证书配置备份设置 总结 前言 因为工作和生活的需要&#xff0c;我一直有博客的搭建需求。我将总结下来&#xff0c;为读者提供参考。  起初&#xff0c;我采用的是香港云虚拟主机&#xff0c;这种虚拟机极其便宜&#xff08;一…