C++ List底层实现

news2024/12/23 8:54:16

文章目录

  • 前言
  • 成员变量
  • 成员函数
    • 迭代器
      • self& operator++()前置++
      • self operator++(int)后置++
      • self operator--()前置--
      • self operator--(int)后置--
      • bool operator!=(const self & tmp)判断是否相等
      • T* operator*() 解引用操作
    • list()初始化
    • iterator begin()
    • iterator end()
    • const_iterator begin()const
    • const_iterator end()const
    • iterator insert(iterator pos, const T& val)在pos位置插入val
    • void push_back(const T& val)在尾部位置后插入
    • void push_front(const T& val)在头部位置插入
    • iterator erase(iterator pos)
    • void pop_back()//删除最后一个元素
    • void pop_front()删除第一个元素
    • list(list<T>& tmp)拷贝构造
    • void swap(list<int>&tmp)交换两个list
    • list< T>& operator=(list< T> it)赋值
    • int size()判断有多少元素
    • bool empty() 判断list是否为空
    • T& front()取首元素
    • T& back()取尾元素
    • void clear()清空元素
    • ~list()析构
    • 再谈迭代器
  • 完整代码
  • 总结


前言

我们都清楚c++中的容器list,本质上就是一个带头双向循环链表,接下来我们实现一下list的底层,帮助我们更深层次的了解list的结构和使用

成员变量

我们知道这个节点有三部分构成 _prev,_next,_val;每个节点看作list的一个元素,这又是一个双向带头循环链表,那么我们如果仅仅知道头节点,就可以访问遍历链表中的所有元素。

//类模板
template < class T>
struct ListNode
{
ListNode(const T& val=T()) //初始化
:_prev(nullptr)
,_next(nullptr)
,_data(val)
{ }
ListNode* _prev;//指向前一个元素
ListNode* _next;//指向后一个元素
T _data;//当前节点的值
};

有了这个结点之后,我们在实现list是创建一个头节点就可以控制这个链表了

template
class list
{
    //模板+类–》类型
    typedef ListNode Node;
private:
    Node* _head;
};

在class类中,默认为私有的,在struct中默认为公有,只有将这个节点定义为公有,我们才可以访问。

那这里采用内部类的方法不行吗??
答案是不行的,如果我们吧struct定义在内部类中,外部list类就无法访问struct中的元素了。

成员函数

迭代器

我们在实现vector中,迭代器我们并没有实现,或者说我们不需要实现,因为vector容容器的结构很特殊,是一块连续的物理空间。加加,解引用等方式很容易实现。

我们看一下迭代器,是一个个地址不连续的指针
在这里插入图片描述

那我们怎末实现呢
我们很清楚迭代器的实质就是一个指针,在这个list中就是哟个listNode的指针。
我们要实现迭代器的操作,可以采用运算符重载的方式实现,但是这里又出现了新的问题,迭代器是一个内置类型,只有自定义类型才可以实现自定义类型重载,所以我们需要都这个指针进行封装,变成一个自定义类型

template< class T >
struct ___list_iterator
{
  typedef ListNode< T> Node;//方便我们使用
  typedef ___list_iterator< T> self;//方便使用,我们会用到
  ___list_iterator(Node* x)//初始化
    :_node(x)
    {}
  Node* _node;//成员变量
};

在这里面实现我们需要的功能,

list::const_iterator it = lt.begin();
while (it != lt.end())
{
std::cout << *it << " ";
++it;
}
std::cout << std::endl;

我们需要实现前置++,后置++,前置- -,后置- -,解引用,判断是否相等。我们依此来看一下

self& operator++()前置++

self& operator++()//++返回的是一个
{
   _node = _node->_next;//我们仅需要让指针往后移动一个节点就可以
  return *this;
}

self operator++(int)后置++

这里只能用self,不能用self&,因为返回的是一个临时对象
self operator++(int)//后置++也是返回一个节点,
{
  self tmp(*this);//首先拷贝一份
  _node = _node->next;//指针向后移动一个节点
  return tmp;//返回拷贝的值
}

self operator–()前置–

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

self operator–(int)后置–

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

bool operator!=(const self & tmp)判断是否相等

bool operator!=(const self & tmp)
{
   return _node != tmp._node;//判断两个指针是否指向同一块空间就可以
}

T* operator*() 解引用操作

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

list()初始化

list()
{
   我们初始化仅仅初始化头节点就可以
   empty_list();//我们可以通过调用这个函数创建头节点
}
void empty_list()
{
   _head = new Node;
   _head->_next = _head;
   _head->_prev = _head;
}

iterator begin()

iterator begin()
{
   return _head->_next;//第一个元素就是头节点后面的那个元素
}

iterator end()

iterator end()
{
   return _head;//最后一个元素的下一个就是head
}

const_iterator begin()const

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

const_iterator end()const

const_iterator end()const
{
   return _head;
}

iterator insert(iterator pos, const T& val)在pos位置插入val

iterator insert(iterator pos, const T& val)
{
   Node* cur = pos._node;//我们首先需要取到迭代器中的元素
   Node* newnode = new Node(val);//创建新节点
   Node* prev = cur->_prev;//保留之前的节点
   prev->_next = newnode;//改变指向
   newnode->_prev = prev;
   newnode->_next = cur;
   cur->_prev = newnode;
   return newnode;//返回新节点
}

在这里插入图片描述
我们来看一下这里的insert有没有迭代器失效的问题??

void push_back(const T& val)在尾部位置后插入

void push_back(const T& val)
{
   insert(end(), val);//我们可以直接进行复用
}

void push_front(const T& val)在头部位置插入

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

iterator erase(iterator pos)

iterator erase(iterator pos)
{
   assert(pos != end());//断言一下,不是删除头节点
   Node* cur = pos._node;//取到迭代器中的元素
   Node* prev = cur->_prev;//记录前一个位置
   Node* next = cur->_next;//记录后一个位置
   delete cur;//释放当前元素
   prev->_next = next;//改变指向
   next->_prev = prev;
   return next;//返回删除元素的下一个位置
}

我们来看一下这里会不会有迭代器失效的问题??
我们发现这里存在迭代器失效的问题,并且很大,这个元素删除了之后,之后很可能还需要用到这个元素,继续删除,如果不及时更新,就会出现大问题。

void pop_back()//删除最后一个元素

void pop_back()
{
   erase(–end());//我们要注意end是哪个位置
}

void pop_front()删除第一个元素

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

list(list& tmp)拷贝构造

在这里我们有很多方式实现,我们来看一种比较简单的
list(list< T>& tmp)
{
   empty_list();//首先创建一个头节点
   //遍历
   for (const auto& e : tmp)
   {
   push_back(e);//取数据依次插入这个新的头结点中
   }
}

void swap(list&tmp)交换两个list

void swap(list< int>&tmp)
{
   std::swap(_head, tmp._head);//我们仅仅改变两个list的指针就可以
}

list< T>& operator=(list< T> it)赋值

list< T>& operator=(list< T> it)
{
   swap(it);
   return *this;
}

我们再来看一下这个过程,我们假设lt1=lt2;
因为list< T> it这里,lt2会产生一份临时拷贝it
在这里插入图片描述
我们swap(it),本质就是将it与lt1的内容进行交换
在这里插入图片描述
it是临时变量,出了作用域就销毁了,我们就完成了赋值任务

int size()判断有多少元素

int size()
{
   int count = 0;//记录一个变量,一个个统计即可
   for (const auto& e : *this)
   {
   count++;
   }
   return count;
}

bool empty() 判断list是否为空

bool empty()
{
   return size() == 0;//我们只需要判断list是否有元素
}

T& front()取首元素

T& front()
{
   return _head->_next->_data;
}

T& back()取尾元素

T& back()
{
   return _head->_prev->_data;
}

void clear()清空元素

一个个删除元素即可
void clear()
{
   iterator it = begin();
   while (it != end())
   {
   it = erase(it);
   }
}

~list()析构

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

再谈迭代器

我们已经实现了普通迭代器,但是对于const都迭代器,我们还需要实现。
我们再来看一下const迭代器,并不是这个迭代器指针不可以修改,而是迭代器指针所指向的内容不可以修改。
那我们是不是可以在普通迭代器的基础上再新增一个const迭代器,我们仅需要修改解引用的那块操作就可以,其他实现的功能都是相同的。

template<class T >
struct ___list_const_iterator
{
	typedef ListNode<T>  Node;
	typedef ___list_const_iterator<T> self;
	___list_const_iterator(Node* x)
		:_node(x)
	{}
	//不等于
	bool operator!=(const self& tmp)
	{
		return _node != tmp._node;
	}
	//解引用
	const T& 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->perv;
		return tmp;
	}
	//需要返回一个地址
	T* operator->()
	{
		return &_node->_data;
	}

	Node* _node;
};

虽然这样可以完成我们的功能,但是总感觉代码有点冗余,const迭代器和普通迭代器有太多重复内容,那我们可不可以通过一种方法将这两种迭代器进行合并呢??

我们来看一下!!!
我们注意到,const T& operator*()和T& operator*()仅仅是返回值不同,我们又不能把两个方法放在同一个迭代器中,我们可以利用传参的方式进行解决,我们来看一下这种操作
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

模板传递的是类型,根据传参的不同调用自己合适的模板参数,这两个是完全不同的类型,我们这样就可以轻松完成我们的工作,仅仅使用一个迭代器就完成了两个功能

完整代码

namespace peng
{
	template <class T>
	struct ListNode
	{
		ListNode(const T& val=T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(val)
		{	}
		ListNode* _prev;
		ListNode* _next;
		T _data;
	};
	//
	//迭代器
	//不连续,
	template<class T,class Ref  >
	struct ___list_iterator
	{
		typedef ListNode<T>  Node;
		typedef ___list_iterator<T,Ref> self;
		___list_iterator(Node* x)
			:_node(x)
		{}
			//不等于
			bool operator!=(const self & tmp)
			{
				return _node != tmp._node;
			}
			//解引用
			Ref 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->perv;
				return tmp;
			}
			//需要返回一个地址
			T* operator->()
			 {
				return &_node->_data;
				
			 }
		
	    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 ___list_iterator<T,T&> iterator;
		typedef ___list_iterator<T,const T&>  const_iterator;

		list()
		{			empty_list();
		}
		void empty_list()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		//l1(l2)
		list(list<T>& tmp)
		{
			empty_list();
			//遍历
			for (const auto& e : tmp)
			{
				push_back(e);
			}
		}
		iterator begin()
		{
			return _head->_next;
		}
		iterator end()
		{
			return _head;
		}
		const_iterator begin()const
		{
			return _head->_next;
		}
		const_iterator end()const
		{
			return _head;
		}
		void swap(list<int>&tmp)
		{
			std::swap(_head, tmp._head);
		}
		list<T>&  operator=(list<T> it)
		{
			swap(it);
			return *this;
		}
		int size()
		{
			int count = 0;
			for (const auto& e : *this)
			{
				count++;
			}
			return count;
		}
		bool empty()
		{
			return size() == 0;
		}
		iterator insert(iterator pos, const T& val)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(val);
			Node* prev = cur->_prev;
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;
			return newnode;
		}
		void push_back(const T& val)
		{
			insert(end(), val);
		}
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* prev = cur->_prev;
			Node* next = cur->_next;
			delete cur;
			prev->_next = next;
			next->_prev = prev;
			return next;
		}
		T& front()
		{
			return _head->_next->_data;
		}
		T& back()
		{
			return _head->_prev->_data;
		}
		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
	private:
		Node* _head;
	};
}

总结

以上就是今天要讲的内容,本文仅仅详细介绍了C++list的模拟实现,希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

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

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

相关文章

年度告警分类统计

1、打开前端Vue项目kongguan_web&#xff0c;完成前端src/components/echart/YearWarningChart.vue页面设计 在YearWarningChart.vue页面添加div设计 <template><div class"home"><div style"margin: 0px auto;height: 100%"><div …

seleniumUI自动化实例(CSDN发布文章)

1.CSDN登陆成功后&#xff0c;点击发布 源码&#xff1a; #点击首页中的发布按钮 CSDNconf.driver.find_element(By.LINK_TEXT,"发布").click() time.sleep(15) 2.输入标题 #输入文章标题&#xff0c;标题格式“selenium UI自动化测试实例今天的日期” CSDNconf.d…

「数据分析」之零基础入门数据挖掘

摘要&#xff1a;对于数据挖掘项目&#xff0c;本文将学习应该从哪些角度分析数据&#xff1f;如何对数据进行整体把握&#xff0c;如何处理异常值与缺失值&#xff0c;从哪些维度进行特征及预测值分析&#xff1f; 探索性数据分析&#xff08;Exploratory Data Analysis&#…

期刊如何反击一波可疑图像

出版商正在部署基于人工智能的工具来检测可疑图像&#xff0c;但生成式人工智能威胁着他们的努力。 期刊正在努力检测用于分析蛋白质和DNA的凝胶的操纵图像。图片来源&#xff1a;Shutterstock 似乎每个月都会有一系列针对研究人员的新高调指控&#xff0c;这些研究人员的论文…

正则表达式具体用法大全

# 正则表达式&#xff1a; ## 单字符匹配&#xff1a; python # 匹配某个字符串&#xff1a; # text "abc" # ret re.match(b,text) # print(ret.group()) # 点&#xff08;.&#xff09;&#xff1a;匹配任意的字符(除了\n)&#xff1a; # text "\nabc&quo…

《论文阅读》带边界调整的联合约束学习用于情感原因对提取 ACL 2023

《论文阅读》带边界调整的联合约束学习用于情感原因对提取 前言简介Clause EncoderJoint Constrained LearningBoundary Adjusting损失函数前言 亲身阅读感受分享,细节画图解释,再也不用担心看不懂论文啦~ 无抄袭,无复制,纯手工敲击键盘~ 今天为大家带来的是《Joint Cons…

Photoshop 2024让图像处理更智能、更高效@

Photoshop 2024是一款功能强大的图像处理软件&#xff0c;广泛应用于创意设计和图像处理领域。它提供了丰富的绘画和编辑工具&#xff0c;包括画笔、铅笔、颜色替换、混合器画笔等&#xff0c;使用户能够轻松进行图片编辑、合成、校色、抠图等操作&#xff0c;实现各种视觉效果…

如何处理WordPress网站域名循环重定向

我在 HostEase 搭建了一个 WordPress 网站。在访问网站时出现了循环重定向的问题。经检查&#xff0c;发现是我在 .htaccess 文件中设置的重定向规则导致的。 重定向循环通常指的是一个网页或者URL地址在不断地进行重定向&#xff0c;最终形成一个循环&#xff0c;导致网页无法…

Monaco Editor系列(一)启动项目与入门示例解析

前言&#xff1a;作为一名程序员&#xff0c;我们工作中的每一天都在与代码编辑器打交道&#xff0c;相信各位前端程序员对 VS Code 一定都不陌生&#xff0c;VS Code 可以为我们提供代码高亮、代码对比等等功能&#xff0c;让我们在开发的时候&#xff0c;不需要对着暗淡无光的…

FreeCAD傻瓜教程之创建参数化几何图形-螺旋体、平面、球体、椭球体、圆柱体、圆锥体、棱柱、椭圆

目的&#xff1a;学会用FreeCAD绘制参数化的几何图形。 一、使用的工作台和工具 1.1选择Part 工作台 1.2单击创建图元...工具 也就是上图黄色工具区域的倒数第2个 1.3 打开几何图元 下方的下拉列表 二、绘制螺旋体、弹簧、螺丝杆 2.1 选择几何图元列表中的 “螺旋体” 设…

opengl日记10-opengl使用多个纹理示例

文章目录 环境代码CMakeLists.txt文件内容不变。fragmentShaderSource.fsvertexShaderSource.vsmain.cpp 总结 环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.…

常见分布式ID解决方案

简介&#xff1a; 分布式ID解决方案是用于在分布式系统中生成唯一标识符的方案。常见的分布式ID解决方案可总结为3点&#xff1a;数据库方案、算法方案、开源组件方案。 分布式ID 分布式 ID&#xff08;Distributed ID&#xff09;是指在分布式系统中生成全局唯一的标识符&…

10000字!一文学会SQL数据分析

文章来源于山有木兮 原文链接&#xff1a;https://edu.cda.cn/goods/show/3412?targetId5695&preview0 第1节 SQL简介与基础知识 做数据分析的&#xff0c;为什么要写SQL&#xff1f; 没有数据的情况下&#xff0c;我们分析数据就像是巧妇难为无米之炊。因此&#xff0c…

【prometheus-operator】k8s监控redis

1、准备exporter https://github.com/oliver006/redis_exporter oliver006-redis_exporter-amd64.tar # 安装镜像 docker load -i oliver006-redis_exporter-amd64.tar # 上传镜像 docker tag oliver006/redis_exporter ip/monitor/redis_exporter:latest docker push ip/mo…

零基础入门数据挖掘系列之「建模调参」

摘要&#xff1a;对于数据挖掘项目&#xff0c;本文将学习如何建模调参&#xff1f;从简单的模型开始&#xff0c;如何去建立一个模型&#xff1b;如何进行交叉验证&#xff1b;如何调节参数优化等。 建模调参&#xff1a;特征工程也好&#xff0c;数据清洗也罢&#xff0c;都是…

强大的文本编辑器:Sublime Text for Mac注册激活版

Sublime Text for Mac是一款功能强大的文本编辑器&#xff0c;特别适合程序员和开发者使用。它提供了丰富的功能&#xff0c;如智能代码补全、语法高亮、自定义快捷键、项目管理、多行选择、自动保存等&#xff0c;以提高代码编写效率和舒适度。此外&#xff0c;Sublime Text还…

【鸿蒙HarmonyOS开发笔记】通知模块之发布基础类型通知,内含如何将图片变成PixelMap对象

通知简介 应用可以通过通知接口发送通知消息&#xff0c;终端用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应用。 通知常见的使用场景&#xff1a; 显示接收到的短消息、即时消息等。 显示应用的推送消息&#xff0c;如广告、版本更新等。 显示当前正…

数字功放VS模拟功放,选择适合你的音频解决方案

数字功放和模拟功放是音频系统中常用的两种功放技术&#xff0c;适用于不同的音频应用&#xff0c;都具有各自的优势和特点。本文将为您详细介绍数字功放和模拟功放的差异&#xff0c;并帮助您找到适合自己的音频解决方案。 1、数字功放是一种利用数字信号处理技术的功放。它将…

Qt 坐标位置转换

Qt 坐标位置转换 文章目录 Qt 坐标位置转换常见的位置坐标转换Qt窗体中常用坐标的区别与获取途径当前光标相对于屏幕的绝对位置当前光标相对于当前窗口的位置鼠标事件发生的位置窗体的位置判断鼠标光标是否悬浮在某个子控件上 从事Qt快一年了 &#xff0c;在做坐标转换的时候容…

钡铼技术R40工业4G路由器加速推进农田水利设施智能化

钡铼技术R40工业4G路由器作为一种先进的通信设备&#xff0c;正在被广泛应用于各行各业&#xff0c;其中包括农田水利设施的智能化改造。通过结合钡铼技术R40工业4G路由器&#xff0c;农田水利设施可以实现更高效的管理和运营&#xff0c;提升农田灌溉、排水等工作效率&#xf…