C++之List模拟实现

news2024/9/22 9:59:18

目录

list的逻辑结构

构造函数

拷贝构造函数

赋值运算符重载

返回迭代器的初始位置

返回迭代器的最终位置

 元素的插入

头插

尾插 

删除元素

头删

尾删

清空整个链表

析构函数

正向迭代器 

反向迭代器

整体代码 


 上期我们学写了list的基本操作,本期我们将学习list的模拟实现。

list的逻辑结构

list是一个带头结点的双向循环链表。

构造函数

	list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

 这是一个无参的构造函数。

        template<class inputiterator>
		list(inputiterator begin, inputiterator end)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			while (begin != end)
			{
				push_back(*begin);
				++begin;
			}
		}

这是用迭代器区间进行初始化的构造函数。

		list(int n, const T& data = T())
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			while (n != 0)
			{
				push_back(data);
				n--;
			}
		}

 初始化链表的n个节点,使它们的每个结点的数据为data的值。

拷贝构造函数

	    list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			list<T> tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}

这是拷贝构造函数的现代写法,通过使用迭代器区间构造函数构造了一个list的临时对象,然后交换了临时对象和当前对象的头结点指针,前提是得保证当前结点的头指针不为空,不然到时候调用析构函数时会将同一空间释放两次,导致出错。

赋值运算符重载

	    list<T>& operator= (const list<T>lt)
		{
			std:swap(_head,lt._head);
			return *this;
		}

 先通过拷贝构造函数生成了一个临时对象,然后再交换了临时对象和当前对象的头结点指针。

返回迭代器的初始位置

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

迭代器的初始位置就是头结点的下一位置。

返回迭代器的最终位置

        iterator end()
		{
			return iterator(_head);
		}

迭代器的最终位置为最后一个元素的下一位置,即头结点的位置。

 元素的插入

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

 表示在当前位置之前插入一个元素,但是当前位置必须为迭代器类型。

元素的插入会导致迭代器失效吗?
不会,因为插入元素之后会及时更改迭代器中节点指针的指向。 

头插

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

	

在头节点之前插入元素,复用了插入函数。 

尾插 

	    void push_back(const T& x)
		{
			insert(end(), x);
		}

在最后一个元素的后面插入元素,复用了插入函数。

删除元素

		iterator erase(iterator pos)
		{
			//前提是不能删除掉头结点

			assert(pos != end());
			Node* prev =pos._node->_prev;
			Node* next = pos._node->_next;
			delete pos._node;
			pos._node = nullptr;
			prev->_next = next;
			next->_prev = prev;
			return iterator(next);
		}

删除某一位置之后,返回的是当前位置的下一位置的迭代器。 

元素的删除会导致迭代器失效吗?
会,因为删除掉当前位置之后,当前迭代器的节点指针会被释放。迭代器的节点都被释放了,所以迭代器自然会失效。

头删

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

尾删

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

清空整个链表

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

删除除头结点之外的所有节点。 

析构函数

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

		}

析构函数会清空整个链表的信息,所以头结点指针也会被释放。

正向迭代器 

正向迭代器,其实就是从前往后进行链表元素的遍历。

	template<class T,class Ref,class Ptr>
	struct list_iterator
	{
		typedef ListNode<T> Node;
		typedef list_iterator<T, Ref, Ptr> self;

		//构造函数
		list_iterator<T,Ref,Ptr>(Node* node)
			:_node(node)
		{
		}
		//解引用操作
		Ref operator*()    
		{
			return _node->_data;
		}

		//->操作,返回迭代器中的节点指针指向的数据的地址
		Ptr operator->()
		{
			return  &_node->_data;
		}

		//前置++
		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;
		}
		//赋值运算符重载,浅拷贝
		self& operator=(const self& iterator)
		{
			_node = iterator._node;
			return *this;
		}
		//判断是否相等
		bool operator ==(const self& iterator) const
		{
			return _node == iterator._node;
		}

		bool operator !=(const self& iterator) const

		{
			return _node != iterator._node;
		}
		Node* _node;

		
	};

list的迭代器和vector的迭代器是不同的,vector的迭代器类型是指针类型,即内置类型。而list的迭代器类型是自定义类型。

反向迭代器

反向迭代器即从后往前进行链表元素的遍历。

namespace yjd {
	template<class iterator,class Ref,class Ptr>
	class reverse_iterator
	{
	public:
		typedef reverse_iterator<iterator, Ref, Ptr> self;
		//构造函数
		reverse_iterator(iterator it)
			:_it(it)
		{
		}
		//解引用操作
		Ref operator*()
		{
			iterator prev = _it;
			return *(--prev);
		}

		//->
		Ptr operator->()
		{
			iterator prev = _it;
			return &(--prev);

		}

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

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

		//判断是否相等
		bool  operator!=(const self & rit) const
		{
			return  _it != rit._it;
		}
	private:
		iterator _it;
	};

}

正向迭代器和返现迭代器的开始和结束位置刚好是相对的。反向迭代器的本质仍然是正向迭代器。反向迭代器的++为正向迭代器的--。

虽然说返现迭代器的开始和结束如图如上图所示,但是我们真正在访问的时候并不是访问所示位置的元素,而是访问其前一个位置的元素,比如要访问rbgin位置的元素,则要让正向迭代器进行--访问最后一个位置的元素。 

整体代码 

list.h

#pragma once
#include<assert.h>
#include"reverse_iterator.h"
namespace yjd 
{
	//节点类型
	template<class T>
	struct ListNode {
		 ListNode* _next;
		 ListNode* _prev;
		T _data;
		ListNode(const T& data=T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(data)
		{
		}
	};

	//链表的迭代器类型
	template<class T,class Ref,class Ptr>
	struct list_iterator
	{
		typedef ListNode<T> Node;
		typedef list_iterator<T, Ref, Ptr> self;

		//构造函数
		list_iterator<T,Ref,Ptr>(Node* node)
			:_node(node)
		{
		}
		//解引用操作
		Ref operator*()    
		{
			return _node->_data;
		}

		//->操作,返回迭代器中的节点指针指向的数据的地址
		Ptr operator->()
		{
			return  &_node->_data;
		}

		//前置++
		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;
		}
		//赋值运算符重载,浅拷贝
		self& operator=(const self& iterator)
		{
			_node = iterator._node;
			return *this;
		}
		//判断是否相等
		bool operator ==(const self& iterator) const
		{
			return _node == iterator._node;
		}

		bool operator !=(const self& iterator) const

		{
			return _node != iterator._node;
		}
		Node* _node;

		
	};



	//链表类型
	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;
		typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
		//构造函数
		list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}

		//构造函数,迭代器区间进行构造
		template<class inputiterator>
		list(inputiterator begin, inputiterator end)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			while (begin != end)
			{
				push_back(*begin);
				++begin;
			}
		}

		//拷贝构造
		list(const list<T>& lt)
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;

			list<T> tmp(lt.begin(), lt.end());
			std::swap(_head, tmp._head);
		}

		//赋值运算符重载
		list<T>& operator= (const list<T>lt)
		{
			std:swap(_head,lt._head);
			return *this;
		}

		//构造函数n个data
		list(int n, const T& data = T())
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
			while (n != 0)
			{
				push_back(data);
				n--;
			}
		}


		//尾插
		//void push_back(const T& data)
		//{
		//	Node* newnode = new Node(data);
		//	Node* tail = _head->_prev;
		//	tail->_next = newnode;
		//	newnode->_prev = tail;
		//	newnode->_next = _head;
		//	_head->_prev = newnode;
		//}
		// 
	


		//rbegin
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(end());
		}

		//rend
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(begin());
		}

	

		//begin
		iterator begin()
		{
			return  iterator(_head->_next);
		}
		//end
		iterator end()
		{
			return iterator(_head);
		}
		//begin
		const_iterator begin() const
		{
			return  const_iterator(_head->_next);
		}
		//end
		const_iterator end() const
		{
			return const_iterator(_head);
		}

		//元素的插入,在pos位置之前插入元素
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* newnode = new Node(x);
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return iterator(newnode);
		}

		//list的头插和尾插,可以复用insert,插入元素之后我们更改的是节点的指针,所以不会涉及到迭代器失效
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

		void push_back(const T& x)
		{
			insert(end(), x);
		}

		//链表删除某一位置元素,删除某一位置的元素,迭代器会失效吗?会失效,因为删除掉元素之后,会释放节点的指针,节点的指针都被释放了,迭代器自然也就没有意义了
		iterator erase(iterator pos)
		{
			//前提是不能删除掉头结点

			assert(pos != end());
			Node* prev =pos._node->_prev;
			Node* next = pos._node->_next;
			delete pos._node;
			pos._node = nullptr;
			prev->_next = next;
			next->_prev = prev;
			return iterator(next);
		}

		//list的尾删头删
		void pop_back()
		{
			erase(--end());
		}

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

		//list整个的删除,这个删除是删除所有的有效的节点
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				erase(it);
				it++;
			}
		}

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

		}

	private:
		Node* _head;

	};



	
}



reverse_iterator.h

#pragma once

 namespace yjd {
	template<class iterator,class Ref,class Ptr>
	class reverse_iterator
	{
	public:
		typedef reverse_iterator<iterator, Ref, Ptr> self;
		//构造函数
		reverse_iterator(iterator it)
			:_it(it)
		{
		}
		//解引用操作
		Ref operator*()
		{
			iterator prev = _it;
			return *(--prev);
		}

		//->
		Ptr operator->()
		{
			iterator prev = _it;
			return &(--prev);

		}

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

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

		//判断是否相等
		bool  operator!=(const self & rit) const
		{
			return  _it != rit._it;
		}
	private:
		iterator _it;
	};

}

以上便是list模拟实现的所有知识点。

本期内容到此结束^_^

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

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

相关文章

使用F1C200S从零制作掌机之构建debian文件系统

前情&#xff1a;使用buildrootfs构建的文件系统调试了很久NES模拟器&#xff0c;执行InfoNES模拟器的时候一直黑屏&#xff0c;无内容显示&#xff0c;调不通了&#xff0c;所以改用debian系统试试。 一、环境配置 首先下载两个工具&#xff1a;qemu-arm-static和debootstra…

SpringSecurity-SpirngBoot-方法级授权(SpringSecurity6.3新特性)(四)

SpringSecurity-SpirngBoot-方法级授权&#xff08;SpringSecurity6.3新特性&#xff09;&#xff08;四&#xff09; 本章使用SpringSecurity6.3新特性实现数据级别的鉴权&#xff0c;主要的目的是实现不同权限的用户查询同一个方法&#xff0c;限制一些内容只能拥有特定权限…

StarRocks分布式元数据源码解析

1. 支持元数据表 https://github.com/StarRocks/starrocks/pull/44276/files 核心类&#xff1a;LogicalIcebergMetadataTable&#xff0c;Iceberg元数据表&#xff0c;将元数据的各个字段做成表的列&#xff0c;后期可以通过sql操作从元数据获取字段&#xff0c;这个表的组成…

Linux--线程的控制

目录 0.前言 1.pthread库 2.关于控制线程的接口 2.1.创建线程&#xff08;pthread_create&#xff09; 2.2.线程等待&#xff08;pthread_join&#xff09; 代码示例1&#xff1a; ​编辑 ***一些问题*** 2. 3.创建多线程 3.线程的终止 &#xff08;pthread_exit /…

python—读写csv文件

目录 csv库方法参数 读取数据 csv.reader方法 文件指定行或列数据读取操作 txt文件的readlines、read方法 csv.DictReader方法 写入数据 txt文件的write&#xff0c;writelines csv.writer方法 csv.DictWriter方法 读写联合(修改及插入数据) 读写csv 文件时&#xf…

语义言语流畅性的功能连接和有效连接

摘要 语义言语流畅性(SVF)受损在多种神经系统疾病中都存在。虽然已经报道了SVF相关区域的激活情况&#xff0c;但这些区域如何相互连接以及它们在脑网络中的功能作用仍存在分歧。本研究使用功能磁共振成像评估了健康被试SVF静态和动态功能连接(FC)以及有效连接。观察到额下回(…

c++初阶学习----入门(上)

大家好啊。最近学习了一点关于c的知识。这不就迫不及待的来与大家分享了嘛。但我这也是现学现卖所以咧。有很多遗落甚至不对的地方希望大家可以在评论区里面指出来。这样也可以增加大家对知识的巩固。 c语言与c的联系 不知道大家看到c会不会不由自主的联想到C语言啊。毕竟都是…

TVBox的Json配置接口编写指南,模板格式说明(如何打造一个专属于自己的TVBox配置文件)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 配置解析 📒📝 JSON基础📝 配置文件结构/参数说明📝 编写步骤📝 注意事项🎈 接口分享⚓️ 相关链接 ⚓️📖 介绍 📖 TVBox 是一款备受欢迎的电视盒子应用(免费影视必备),它以其高度自定义的特性深受用户喜爱…

Pearson 相关系数的可视化辅助判断和怎么用

Pearson 相关系数的可视化辅助判断和怎么用 flyfish Pearson 相关系数 是一种用于衡量两个连续型变量之间线性相关程度的统计量。其定义为两个变量协方差与标准差的乘积的比值。公式如下&#xff1a; r ∑ ( x i − x ˉ ) ( y i − y ˉ ) ∑ ( x i − x ˉ ) 2 ∑ ( y i −…

jitsi 使用JWT验证用户身份

前言 Jitsi Meet是一个很棒的会议系统,但是默认他运行所有人创建会议,这样在某种程度上,我们会觉得他不安全,下面我们就来介绍下使用JWT来验证用户身份 方案 卸载旧的lua依赖性sudo apt-get purge lua5.1 liblua5.1-0 liblua5.1-dev luarocks添加ubuntu的依赖源,有则不需…

AI时代算法面试:揭秘高频算法问题与解答策略

三种决策树算法的特点和区别 ID3算法&#xff1a;基本的决策树算法&#xff0c;适用于简单的分类问题C4.5算法&#xff1a;改进了ID3算法&#xff0c;适用于更复杂的分类问题&#xff0c;可以处理连续型数据和缺失值CART算法&#xff1a;更加通用的决策树算法&#xff0c;适用于…

住宅代理、移动代理和数据中心代理之间的区别

如果您是一名认真的互联网用户&#xff0c;可能需要反复访问某个网站或服务器&#xff0c;可能是为了数据抓取、价格比较、SEO 监控等用例&#xff0c;而不会被 IP 列入黑名单或被 CAPTCHA 阻止。 代理的工作原理是将所有传出数据发送到代理服务器&#xff0c;然后代理服务器将…

用LangGraph、 Ollama,构建个人的 AI Agent

如果你还记得今年的 Google I/O大会&#xff0c;你肯定注意到了他们今年发布的 Astra&#xff0c;一个人工智能体&#xff08;AI Agent&#xff09;。事实上&#xff0c;目前最新的 GPT-4o 也是个 AI Agent。 现在各大科技公司正在投入巨额资金来创建人工智能体&#xff08;AI …

VBA实现Excel的数据透视表

前言 本节会介绍通过VBA的PivotCaches.Create方法实现Excel创建新的数据透视表、修改原有的数据透视表的数据源以及刷新数据透视表内容。 本节测试内容以下表信息为例 1、创建数据透视表 语法&#xff1a;PivotCaches.Create(SourceType, [SourceData], [Version]) 说明&am…

面对数据不一致性的解决方案:

polarDB是读写分离和计算存储分离的分布式数据库&#xff0c;并且副本的log replicate是基于Parallel-Raft协议来实现的。所以在瞬时进行写和读的操作时&#xff0c;是不可避免会存在数据一致性问题&#xff0c;导致这个数据一致性问题的原因不是事务&#xff0c;而是多副本日志…

【考研数学】李林《880题》25版听说大改版?和和24版差别大吗?

25版和24版总体差别不大&#xff0c;只有小部分内容有所变动&#xff01; 拓展题部分的更新&#xff1a;25版在拓展题部分进行了一些更新&#xff0c;从李林的模拟题中挑选了大约40道题目添加到新版中。 高等数学&#xff1a;变动主要集中在前三章&#xff0c;但具体的题目变…

【C++】开源:坐标转换和大地测量GeographicLib库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍坐标转换和大地测量GeographicLib库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关…

Facebook社交平台的未来发展趋势分析

随着科技和社交需求的不断演变&#xff0c;Facebook作为全球最大的社交平台之一&#xff0c;其未来发展的趋势备受关注。从技术创新到社会影响&#xff0c;Facebook正在经历着前所未有的变化和挑战。本文将探讨Facebook未来发展的几个关键趋势&#xff0c;并分析其可能的影响和…

SpringBoot 实现视频分段播放(通过进度条来加载视频)

需求&#xff1a;现在我本地电脑中有一个文件夹&#xff0c;文件夹中都是视频&#xff0c;需要实现视频播放的功能。 问题&#xff1a;如果通过类似 SpringBoot static 文件夹的方式来实现&#xff0c;客户端要下载好完整的视频之后才可以播放&#xff0c;并且服务端也会占用大…

Androidstudio开发,天气预报APP

1.项目功能思维导图 2. 项目涉及到的技术点 数据来源&#xff1a;和风天气API使用okhttp网络请求框架获取api数据使用gson库解析json数据使用RecyclerViewadapter实现未来7天列表展示和天气指数使用PopupMenu 实现弹出选项框使用动画定时器实现欢迎页倒计时和logo动画使用Text…