C++手撕简易list

news2024/9/21 12:32:06

目录

节点的准备

list类

push_back

stl容器的遍历和修改

begin

end

!=   

++

重载*

效果展示: 

const迭代器

方法一:

方法二:

->的重载

insert

push_front

erase 

展示效果

 pop_back && pop_front

 效果展示

clear()&& 析构函数


节点的准备

我们在创建节点的时候,选用struct,而不是class,因为struct是默认公有的,我们在进行类成员访问的时候就不会受到约束

	template<class T>
	struct ListNode {
		ListNode<T>* _prev;
		ListNode<T>* _next;
		T _data;

		ListNode(const T& x=T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(x)
		{}
	};

为什么在初始化的时候,传值的时候用const T& x = T(),初始化的时候也是要用x

        因为我们在创建链表的时候,不一定就是整型链表,可能是double链表,可能string链表,甚至有可能是自定义链表

        所以试想一下,0可以作为string的初始值吗,显然是不能的

        我们又知道,现在内置类型现在也支持初始化了,所以为了保证不会出错,统一采用T()初始化

知道了节点的基本属性后,就可以利用节点类(ListNode类)来创建链表(list类)了


list类

        因为ListNode是struct,其类成员是公开的,所以在创建list类的时候,所以可以在类里面访问ListNode成员

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

 

push_back

    void push_back(const T& x) {
		Node* newnode = new Node(x);
		Node* tail = _head->_prev;

		tail->_next = newnode;
		newnode->_prev = tail;
		newnode->_next = _head;
		_head->_prev = newnode;
	}

 在push_back成功后,我们该如何访问遍历我们的list呢

我们访问的方式普遍是用迭代器


stl容器的遍历和修改

        我们在之前比如string,vector之类的,在底层实现的迭代器直接通过使用指针即可完成目的,原因就是它们的空间地址在底层是连续的但是链表的空间地址的底层不连续,也就是说我们通过使用指针++得到的指针指向的下一个数据可能不是下一个节点

        我们又该如何实现它呢?

        我们可以创建一个迭代器类,通过迭代器来实现链表的遍历访问,将节点交给迭代器类(__list_iterator),从而实现访问效果

		list<int>::iterator it = l1.begin();
		while (it != l1.end()) {
			cout << *it << " ";
			it++;
		}
		cout << endl;

那么我们就会知道我们缺少begin, end, 重载!= ,重载++,以及重载*

begin

因为此链表是带有哨兵位,所以一开始访问的地方应该是哨兵位的下一位,即_head->_next;

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

end

我们需要在end位置结束,end又是带头双向循环列表,所以我们要在链表访问绕一圈后再回来之时在头节点处停止,即_head;

		iterator end() {
			return _head;
		}

 

!=   

我们知道内置类型的是可以直接使用运算符,但是自定义类型则需要自己重新重载

需要注意的是这些运算符的比较是在迭代器类里,即__list_iterator类,所以不能在list类里重载

其次,比较也是通过迭代器类比较,创建的it是属于迭代器的,即iterator it

比什么不同呢,比地址不同就可以了

		bool operator!=(const self& x) {
			return _node != x._node;
		}

++

        ++依旧分为前置和后置,很简单,令其指向下一节点即可,唯一的是后置++需要返回的是不变的值,返回不变值的话,就需要一个值来代替,tmp

		self& operator++() {
			_node = _node->_next;
			return *this;
		}

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

         关于self& operator++(int)中,self tmp(*this)里,一开始我的疑惑是:欸,人家_node不是指针类吗,拷贝构造我也没写,用它进行拷贝构造的话,只会进行值拷贝哇,不会进行深度拷贝呀。

        到后面想想,在实现list类里,我们本身就是需要通过迭代器实现间接控制list的内容,所以要修改的东西也同样一样

        不写析构函数也是如此道理,迭代器不用析构,因为这些成员都是类模板的,只是借给你用一下的,结果没道理说你用了之后还把我的成员给释放掉

重载*

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

效果展示: 

 

 


const迭代器

	void print_list(const list<int>& lt) {
		list<int>::iterator it = lt.begin();
		while (it != lt.end()) {
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

 当我们传const类型的对象的时候,因为我们写的end,begin等等是非const的

那么我们便会想通过给list加const来改变此情况

const list<int>::iterator it = lt.begin();

报出大量的错误,究其原因 

const对象it不能够对其进行修改,因此it++是错误的 

相对于我们想要的const类型的迭代器,我们想要的是it能够进行++,而不是相反,我们要对其进行遍历,只是不要对里面的内容进行修改而已,所以我们在list<T>::iterator it 前面加上const,就直接限定了我连遍历都不能遍历

那我们应该如何做到我们能够遍历链表,但又不会修改里面的值呢,即实现const版的iterator

 

方法一:

实现一个const_iterator,在iterator的基础上,复制粘贴一份,唯一需要改变的就是operator*

    template<class T>
	struct __list_const_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_const_iterator<T> self;
		Node* _node;

		__list_const_iterator(Node* node)
			:_node(node)
		{}

		self& operator++() {
			_node = _node->_next;
			return *this;
		}

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

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

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

		const  T& operator*() {
			return _node->_data;
		}

		bool operator!=(const self& s) {
			return _node != s._node;
		}

		bool operator==(const self& s) {
			return _node == s._node;
		}

	};

但是这样有会显得很冗余,因为不同的只是类名跟operator* 

那么我们的解决方法是:

方法二:

在原来的类模板上增加一个参数 

将__list_const_iterator 舍弃掉,将__list_iterator中的类模板从template<class T>变为template<class T,class Ref>

将operator的返回类型在重新定义一下

在list类中,对 iterator 和 const_iterator 重新typedef一下

经过此番操作,我们就可以将原本需要两个类来实现的遍历通过修改类模板参数,能够整合到一个类里面


->的重载

当我们的链表存储的不是内置类型的时候,或者链表存储的结构能够存放多个数值,比如类,结构体之类的时候,我们就会通常用 -> 来进行访问我们想要的数值

先看一串代码

struct BB {
	int _b1;
	int _b2;

	BB(int b1=1,int b2=1)
		:_b1(b1)
		,_b2(b2)
	{}
};

int main() {
	list<BB> l;
	l.push_back({1,1});
	l.push_back({2,2});
	l.push_back({3,3});

	list<BB>::iterator it = l.begin();
	while (it != l.end()) {
		cout << it->_b1 << "," << it->_b2 << endl;
		it++;
	}
	return 0;
}

上面这串代码,在STL模板类当中是很自然的,没有任何问题

但在我们的手撕链表中,好像就出现了些许问题

编译器给出的解释也是比较明显了

不是类成员,没有重载等,所以我们在自定义list的时候,需要我们自己重载一下operator->

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

其在对上 it->_a1 或者 it -> _a2 的原型就是

cout << it.operator->()->_a1 << endl;
cout << it.operator->()->_a2 << endl;

因此本来应该是两个箭头的,但是为了可读性省略一个箭头,即原来是it -> -> _a1,现在为了可读性变成了 it->_a1 

当然箭头所表示的指针也有const和 非const,因此要对类模板参数进行扩充

	template<class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef ListNode<T> Node;
		typedef __list_iterator<T,Ref,Ptr> self;
		Node* _node;
        
        ...

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

    
    template<class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		typedef __list_iterator<T,T&,T*> iterator;
		typedef __list_iterator<T,const T&,const T*> const_iterator;
        ...

    }

 


insert

先写insert是因为,后面的其它函数可以适当的复用insert

要记录下插入位置的前一个节点,不然随意更换的话,会找不到前一个节点

		iterator insert(iterator pos,const T& x) {
			Node* newnode = new Node(x);
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			cur->_prev = newnode;
			newnode->_next = cur;
			prev->_next = newnode;
			newnode->_prev = prev;

			return newnode;
		}

 

push_front

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

 

erase 

删除某一节点

		iterator 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;
			return next;
		}

展示效果

 

 

 pop_back && pop_front

		void pop_back() {
			erase(--end());
		}

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

在实现pop_back之前,我们还要实现一下重载--

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

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

 效果展示

 

clear()&& 析构函数

		void clear() {
			iterator it = begin();
			while (it != end()) {
				it = erase(it);
			}
		}

		~list() {
			clear();
			delete _head;
			_head = nullptr;
		}

以上便是此次博文的学习内容,如有错误,还望大佬指正,谢谢阅读!

 

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

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

相关文章

【 html+css 绚丽Loading 】000030 灵文闪烁符

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

【C++】继承相关知识详细梳理

1.继承简介 什么是继承&#xff1a; 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。 继承呈现了面向对象程序设计的层…

python破解[5分钟解决拼多多商家后台字体加密]

可【QQ群】拿源码 进入经营总览想把数据存下来发现返回的json数据部分空白如下 这可怎么办 稳住应该是字体的问题&#xff0c;可能是多多自己实现了某种字体&#xff0c;我们去找他的js 发现如我们所想&#xff0c;进行跟踪&#xff0c;发现的确是在css端进行了字体替换&am…

报警规范管理

报警规则管理 想要获取报警数据&#xff0c;我们首先必须先制定报警规则&#xff0c;会根据不同的设备&#xff0c;不同的物模型来定义报警规则 需求分析 我们先来分析需求&#xff0c;打开原型图 数据来源&#xff1a; 逻辑规则&#xff1a; 1&#xff09;若多条报警规则是…

CSS-层叠上下文【看这一篇就够了!!!】

目录 前序 z-index设置定位元素层叠顺序 z-index值相同时&#xff0c;写在后面的覆盖写在前面的 z-index值越大&#xff0c;越在上面显示 z-index值为负数 CSS中的层叠上下文 什么是“层叠上下文” 层叠上下文的创建 根层叠上下文 定位元素的传统层叠上下文 层叠顺序…

15:发光二极管布局要求

1.指示灯一般放正面&#xff0c; 靠板边

【Android】Material Design编写更好的UI

Toolbar 对于控件ActionBar我们非常熟悉&#xff0c;就是我们常见的标题栏&#xff0c;但ActionBar只能位于活动的顶部&#xff0c;因此我们更建议使用Toolbar。在新建一个项目的时候都是默认显示ActionBar&#xff0c;我们要使用Toolbar就需要先将标题栏改为不显示 先来看看…

在Ubuntu上使用apt工具安装RabbitMQ

创建安装脚本 cd home/ madir scripts cd scripts 创建脚本前&#xff0c;需要确认Linux版本。不同的版本对应着不同的运行脚本。 lsb_release -a 查看Linux版本 可以看到&#xff0c;我的Ubuntu版本是22.04。 在这里找到对应的脚本复制。 创建脚本文件&#xff1a; ca…

详解树状数组(C/C++)

树状数组&#xff08;Binary Indexed Tree&#xff0c;简称BIT或Fenwick Tree&#xff09;是一种用于高效处理数据序列的算法数据结构。它能够支持两个主要操作&#xff1a;单点更新和区间求和&#xff0c;这两个操作的时间复杂度都能达到O(log n)&#xff0c;其中 n 是数据序列…

STM32基础篇:SPI片上外设

SPI外设简介 STM32芯片内部集成了SPI片上外设&#xff0c;可由硬件自动执行时钟生成、数据收发等功能&#xff0c;减轻CPU负担。对于STM32F103C8T6&#xff0c;其SPI资源有SPI1、SPI2。 一些参数配置&#xff1a; 8位/16位数据帧高位先行/低位先行时钟频率&#xff1a;PCLK/…

vue3本地运行错误集

1、解决报错ValidationError: Progress Plugin Invalid Options问题 ValidationError: Progress Plugin Invalid Optionsoptions should NOT have additional propertiesoptions should NOT have additional propertiesoptions should NOT have additional propertiesoptions …

SMART PLC 脉冲输出指令PLS应用

200SMART PLC如何实现可调频率可调占空比PWM输出 200smart_PLC如何实现可调频率可调占空比PWM输出_200smart pwm-CSDN博客文章浏览阅读6.4k次,点赞2次,收藏7次。本文介绍了如何在SMART PLC中通过修改原向导接口,实现可调频率和占空比的PWM输出。详细阐述了脉冲周期、占空比…

DMA简述与使用实例

之后要学&#xff1a;SPI / IICDMA 学习的这位up主的视频&#xff1a;全网最清楚的DMA讲解&#xff0c;三种搬运模式三个例子讲清楚&#xff08;STM32教程基于HAL库和CUBEIDE&#xff09;_哔哩哔哩_bilibili 目录 01-基本信息 1-概述 2-方向 3-模式 正常模式 轮询模式 …

学习日志8.30--防火墙NAT

目录 一、实验环境配置 二、配置防火墙静态NAT一对一 三、配置防火墙静态NAT多对多 四、配置防火墙NAT端口转换NAPT 五、防火墙smart-nat、easyip 六、防火墙三元组NAT 在学习过基于路由器的NAT网络地址转换&#xff0c;现在学习基于防火墙NAT的网络地址转换&#xff0c;…

python-读写Excel:xlwings库操作

几种操作Excel的python库对比 安装:pip install xlwings 目录 APP实例化对象 工作薄对象 创建工作薄 打开工作薄 工作薄属性 工作表对象 新增工作表 复制表 获取工作表对象 工作表属性 删除和清除表数据及表格式 工作表行高列宽(自动调整) 单元格对象 获取单元…

【hot100篇-python刷题记录】【旋转图像】

R7-矩阵篇 印象题&#xff1a; 思路&#xff1a; 先转置&#xff0c;转置完我们按照列的中间进行对称交换就可以了。 class Solution:def rotate(self, matrix: List[List[int]]) -> None:"""Do not return anything, modify matrix in-place instead.&qu…

【微机原理】指令JZ和JNZ的区别

&#x1f31f; 嗨&#xff0c;我是命运之光&#xff01; &#x1f30d; 2024&#xff0c;每日百字&#xff0c;记录时光&#xff0c;感谢有你一路同行。 &#x1f680; 携手启航&#xff0c;探索未知&#xff0c;激发潜能&#xff0c;每一步都意义非凡。 JZ&#xff08;Jump …

祝贺严建兵教授任华中农业大学校长

公众号&#xff1a;生信漫谈&#xff0c;获取最新科研信息&#xff01; 祝贺严建兵教授任华中农业大学校长https://mp.weixin.qq.com/s?__bizMzkwNjQyNTUwMw&mid2247487040&idx1&sn6800055c9944754be11dc77a30ee1906&chksmc0e9ebb0f79e62a64634d5cd057578ca5…

Java 入门指南:Java 并发编程 —— AQS、AQLS、AOS 锁与同步器的框架

AQS AQS 是 AbstractQueuedSynchronizer 的缩写&#xff0c;即 抽象队列同步器&#xff0c;是 Java.util.concurrent 中的一个基础工具类&#xff0c;用于实现同步器&#xff08;Synchronizer&#xff09;的开发。 AQS 提供了一种实现锁和同步器的框架&#xff0c;使用 AQS 能…

Mysql高级篇(上)

Mysql高级篇&#xff08;上&#xff09; Mysql架构介绍(一)1、Linux环境下的MySQL的安装与使用2、MySQL请求到响应字符集变化&#xff08;了解&#xff09;3、MySQL8 的主要目录结构4、数据库和文件系统关系&#xff08;1&#xff09;默认数据库&#xff08;2&#xff09;数据库…