list部分接口模拟实现(c++)

List

  • list简介
  • list基本框架
    • list构造函数
      • list_node结构体的默认构造
      • list类的默认构造
    • push_back()
    • iteartor迭代器
    • 迭代器里面的其他接口
      • const迭代器
      • 通过模板参数实现复用
      • operator->()
    • insert()
    • erase()
    • clear()
    • 析构函数
    • 迭代器区间构造
    • 拷贝构造
    • operator=()

list简介

- list可以在常数范围内在任意位置进行插入和删除的序列式容器,并且容器可以双向迭代。
- list底层是一个带头双向链表结构,通过指针连接前一个和后一个元素。
- list支持在任意位置进行插入、删除元素,效率更好。
- list不支持随机访问

list基本框架

namespace xty
{
	//带头双向循环链表
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
	};


	template<class T>
	class list
	{
		typedef list_node<T> node;


	private:
		node* _head;//头指针

	};
}

list构造函数

我们实现一个无参构造函数,但是在这之前我们需要做一些准备工作,先实现一个申请list_node的构造函数,在struct类里面实现。

list_node结构体的默认构造

	//带头双向循环链表
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _data;
		//在创建list_node变量时,自动调用构造
		list_node(const T& val = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_data(val)
		{}
	};

为什么不使用class,而使用struct呢?
因为我们想达到这样一个目的:想要让别人自由的访问自己的成员函数,不做任何限制,就用struct。而list_node,list是要经常使用的,因此使用struct修饰list_node比较方便。

list类的默认构造

仅仅申请一个空的头结点,next都指向头

在这里插入图片描述

		list()
		{
			//两种申请方法都可以
			//_head = new list_node<T>
			_head = new node;
			_head->next = _head;
			_head->prev = _head;
			//_head->_data已经在new的时候调用构造了
		}

push_back()

先记录一下尾结点,插入更简单。

		void push_back(const T& x)
		{
			//留记录尾结点
			node* tail = _head->_prev;
			node* new_node = new node(x);//传入x值

			tail->_next = new_node;
			new_node->_next = _head;

			_head->_prev = new_node;
			new_node->_prev = tail;

		}

iteartor迭代器

整体框架:

	//iteartor
	template<class T>
	struct __list_iterator
	{
		typedef list_node<T> node;
		node* _node;//成员变量

		//构造函数
		__list_iterator(node* x)
			:_node(x)
		{}


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

		//++返回相应的迭代器
		__list_iterator<T> operator++()
		{
			_node = _node->_next;
			return *this;
		}

		//是位置是否相等,不是内容是否相等。
		bool operator!=(__list_iterator<T> x)
		{
			return _node != x._node;
		}

	};



	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:

		//迭代器重命名
		typedef __list_iterator<T> iterator;
	public:
		//仅仅申请一个空的头结点,next都指向头
		list()
		{
			//两种申请方法都可以
			//_head = new list_node<T>
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
			//_head->_data已经在new的时候调用构造了了
		}

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

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

语法细节理解:

  1. 为什么把iterator和list进行单独封装?写一块不好吗?
    首先list只会用到迭代器的begin()和end()函数。其他的像++,其实都是通过迭代器的对象调用的(并不是list的对象调用的)。把iterator从list中抽离出来,不仅可以减少list的复杂性,还能让人更加清楚,迭代器其实也是一个模板,它能被抽离出来,用以实现各种数据结构的迭代!而list内部的begin和end接口,千篇一律。这样就达到的封装的目的!所以,还是分开写的好,逻辑更清晰,更容易维护。
  2. struct __list_iterator结构体里面为什么要写构造函数呢?
    在c++里struct也被当做是类!类里有构造函数很正常,还可以有拷贝构造呢!只不过struct默认是public的。这样我们在声明该类型的变量时,函数会自动调用构造函数,使该结构体的数据自动是被初始化过的。
	xty::list<int>::iterator it = lt.begin();  //调用对象需要用list
	//xty::list<int>::iterator it(lt.begin());  //调用对象需要用list
	while (it != lt.end())
	{
		cout << *it << endl;
		++it;
	}

写了构造函数之后,第二种声明方式也是可以的。而第一种方式其实调用的是拷贝构造函数,但是编译器给优化成了拷贝构造,我们没有写拷贝构造,编译器会调用默认的拷贝构造,是一个浅拷贝。但是我们不是经常说浅拷贝会造成析构问题?这里为什么不会?因为我们没有写析构函数,而且析构函数。为什么不写析构函数呢?因为没有什么可以析构的,函数的结点是list里的内容,不需要迭代器的析构函数管,因此我们不写析构函数。

  1. 迭代器++返回的是迭代器的类型。
  2. 注意:_list_iterator是类名,_list_iterator才是类型!
  3. list里面的begin要返回迭代器类型,然而怎么由指针转化成迭代器类型呢?要利用迭代器的构造函数来返回。

迭代器里面的其他接口

		bool operator==(const __list_iterator<T>& x)
		{
			return _node == x._node;
		}
		//i++
		__list_iterator<T> operator++(int)
		{
			__list_iterator<T> tem(*this);
			_node = _node->_next;
			return tem;
		}


		__list_iterator<T> operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		__list_iterator<T> operator--(int)
		{
			__list_iterator<T> tem(*this);
			_node = _node->_prev;
			return tem;
		}

语法细节理解:

  1. 注意迭代器传进来的参数基本上都是迭代器,如果不更改,最好加上const和&。
  2. 如果命名空间冲突,注意在函数声明和定义的时候也要加上空间名。
void Print(xty::list<int>& lt);
  1. 我们发现__list_iterator 有点长,我们重命名一下。
	//iteartor
	template<class T>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T> self;
		node* _node;//成员变量

		//构造函数
		__list_iterator(node* x)
			:_node(x)
		{}

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

		//++返回相应的迭代器
		self operator++()
		{
			_node = _node->_next;
			return *this;
		}

		//是位置是否相等,不是内容是否相等。
		bool operator!=(const __list_iterator<T>& x)
		{
			return _node != x._node;
		}

		bool operator==(const __list_iterator<T>& x)
		{
			return _node == x._node;
		}
		//i++
		self operator++(int)
		{
			self tem(*this);
			_node = _node->_next;
			return tem;
		}


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

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

	};

const迭代器

要实现const迭代器只需要再写一个const类即可。
记住是指针可以修改,但是内容不可以修改,因此不能在this那加const。

		//迭代器重命名
		typedef __list_iterator<T> iterator;
		typedef const__list_iterator<T> const_iterator;
	public:
		const_iterator begin()const
		{
			const_iterator it(_head->_next);
			return it;
		}


		const_iterator end()const
		{
			return const_iterator(_head);
		}

list里面的迭代器修改仅仅需要,typedef一下,然后将构造函数改成所需要的const类型即可。


而我们需要再实现一个const类型的iterator

	template<class T>
	struct const__list_iterator
	{
		typedef list_node<T> node;
		typedef const__list_iterator<T> self;
		node* _node;//成员变量

		//构造函数
		const__list_iterator(node* x)
			:_node(x)
		{}

		const T& operator*()  //值不仅可以修改!!
		{
			return _node->_data;
		}

		//++返回相应的迭代器
		self operator++()  //指针可以修改!!
		{
			_node = _node->_next;
			return *this;
		}

		//是位置是否相等,不是内容是否相等。
		bool operator!=(const self& x)const
		{
			return _node != x._node;
		}

		bool operator==(const self& x)const
		{
			return _node == x._node;
		}
		//i++
		self operator++(int)  //指针可以修改!!!
		{
			self tem(*this);
			_node = _node->_next;
			return tem;
		}


		self operator--()  //指针可以修改!!!
		{
			_node = _node->_prev;
			return *this;
		}

		self operator--(int)  //指针可以修改!!!
		{
			self tem(*this);
			_node = _node->_prev;
			return tem;
		}
	};

问题:代码写完之后,我们发现实际上只有operator*()的返回值加了const,其他的地方没有改,因此,我们利用模板参数赋用解决问题。

通过模板参数实现复用

	template<class T,class Ref>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T,Ref> self;
		node* _node;//成员变量

		//构造函数
		__list_iterator(node* x)
			:_node(x)
		{}

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

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:

		//迭代器重命名
		typedef __list_iterator<T, T&> iterator;
		typedef __list_iterator<T,const T&> const_iterator;
	public:
		//仅仅申请一个空的头结点,next都指向头
		list()
		{
			//两种申请方法都可以
			//_head = new list_node<T>
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
			//_head->_data已经在new的时候调用构造了了
		}

	}

在这里插入图片描述

通过增加类模板参数,这种问题被很巧妙的解决了。请好好体会!

operator->()

当我们遇到自定义类型数据链表时,访问数据就会比较麻烦。

	struct AA
	{
		int _a1;
		int _a2;

		AA(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{}
	};


	void test_aa()
	{
		xty::list<AA> lt;
		lt.push_back(AA(1, 1));
		lt.push_back(AA(2, 2));
		lt.push_back(AA(3, 3));
		lt.push_back(AA(4, 4));

		xty::list<AA>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << (*it)._a1 << ":" << (*it)._a2 << endl;
			++it;
		}
	}

如上例子所示,cout方式,在这里很是别扭,因为it是迭代器,我们能不能通过重载->来访问这样的数据呢?
显然是可以的!如下:

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

所以我们通过如下方式打印链表的数据:

		xty::list<AA>::iterator it = lt.begin();
		while (it != lt.end())
		{
			//cout << (*it)._a1 << ":" << (*it)._a2 << endl;
			cout << it->_a1 << ":" << it->_a2 << endl;
			++it;
		}

但是这个代码是不是有一点别扭?没看出来的话,我再打印一次:

			//两种打印方式一样!!!
			cout << it->_a1 << ":" << it->_a2 << endl;
			cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

在这里插入图片描述
==解释:==之所以出现这样的原因,是因为编译器自动把两个连续的->优化成一个->,防止观感上的差异,这样设计能让人立马看出这个其实是在访问AA的数据。


为了适应const和非const两种迭代器,我们再设计一个模板参数。如下实例:

	//iteartor
	template<class T,class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T,Ref,Ptr> self;
		node* _node;//成员变量
		Ptr operator->()
		{
			return &(_node->_data);
		}
...................
	}
template<class T>
	class list
	{
		typedef list_node<T> node;
	public:

		//迭代器重命名
		typedef __list_iterator<T, T&, T*> iterator;
		typedef __list_iterator<T,const T&,const T*> const_iterator;
	public:

insert()

		//在pos位置前插入,返回插入位置的迭代器
		iterator insert(iterator pos, const T& x)
		{
			node* new_node = new node(x);
			
			node* cur = pos._node;
			node* prev = cur->_prev;

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

			return iterator(cur);
		}

erase()

返回删除元素的下一个位置的迭代器。

		iterator erase(iterator pos)
		{

			//不能删头
			assert(pos._node!=_head);
			node* cur = pos._node;
			node* prev = cur->_prev;

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

			delete cur;
			return iterator(prev->_next)
		}

注意:删除元素后,pos位置的数据就被删除了,会产生迭代器失效问题,如果再使用pos需要重新赋值。

clear()

清空list的内容,可以借助迭代器和erase()来删除。

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

析构函数

结合clear()来编写。

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

迭代器区间构造

因为迭代器区间构造,也需要一个头结点,所以,我们方便复用,又定义了一个初始化函数,具体改动如下:

		list()
		{

			empty_init();
			//两种申请方法都可以
			//_head = new list_node<T>
			//_head = new node;
			//_head->_next = _head;
			//_head->_prev = _head;
			//_head->_data已经在new的时候调用构造了了
		}


		void empty_init()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;

		}
		template<class Iterator>
		list(Iterator first, Iterator last)
		{
			empty_init();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

拷贝构造

提供了两种方法,第一种方法效率较低,第二种利用swap提高了效率。

		lt2(lt)
		//list(const list<T>& lt)
		//{
		//	empty_init();
		//	for (auto e : lt)
		//	{
		//		push_back(e);
		//	}
		//}

		void swap(list<T>& tem)
		{
			std::swap(_head, tem._head);
		}

		list(const list<T>& lt)
		{
			empty_init();//初始化this
			list<T> tem(lt.begin(), lt.end());
			swap(tem);
		}

operator=()

实现较为简单。


			//注意这里不能传引用
			list<T>& operator=(list<T> lt)
			{
				swap(lt);
				return *this;
			}


最后一个问题:

const list<int> lt;

这行代码能调用构造函数吗?
答案是肯定的,因为变量在最开始是没有const属性的,定义好了以后,才有const属性。否则const都没法定义出来了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一篇简述 Linux 移植与系统启动

1、Linux系统启动与U-Boot 所谓移植就是把程序代码从一种运行环境转移到另一种运行环境。对于内核移植来说&#xff0c;主要是从一种硬件平台转移到另一种硬件平台上运行。 体系结构级别的移植是指在不同体系结构平台上Linux内核的移植&#xff0c;例如&#xff0c;在ARM、MI…

前端项目导入vue和element

1.安装nodejs 下载链接https://cdn.npmmirror.com/binaries/node/v18.18.0/node-v18.18.0-x64.msi 进入cmd 命令行模式 管理员身份运行 输入 &#xff08;node -v&#xff09;能看到版本号 npm config set prefix "C:\Program Files\nodejs" 默认路径 npm config…

补偿 IIR 滤波器引入的延迟

补偿 IIR 滤波器引入的延迟 对信号进行滤波会引入延迟。这意味着相对于输入&#xff0c;输出信号在时间上有所偏移。 无限冲激响应滤波器对某些频率分量的延迟可能比其他频率分量更长。它们会使输入信号呈现明显失真。函数 filtfilt 可补偿此类滤波器引入的延迟&#xff0c;从…

asp.net校园招聘管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net 校园招聘管理系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言开发 应用技术&#xff1a;asp.net c#s…

PHP编写采集药品官方数据的程序

在 PHP 中编写爬虫程序&#xff0c;首先我们需要引入一些必要的库&#xff0c;如 curl 和 file_get_contents。然后&#xff0c;我们需要设置爬虫ip信息&#xff0c;以便我们可以从指定的爬虫ip服务器上获取数据。 // 引入必要的库 require_once curl.php;// 设置爬虫ip信息 $p…

【vue+el-upload+vue-cropper】vue图片上传,vue-cropper图片裁剪后上传

一. 先看效果演示 二. 图片上传 用的el-upload加el-image组件 html部分 <el-dialog> ...//无关代码已省略<div v-for"item in imgArr" :key"item.index"><span>{{ item.name }}</span><el-upload action"#" list-t…

客服呼叫中心的语音质检工作

语音质检是呼叫中心运营中必不可缺少的一个环节&#xff0c;呼叫中心语音质检对坐席起着直接监督的作用&#xff0c;也正是这种监督约束推动着客服人员不断提升自身的业务能力。 而客服呼叫中心的质检结果中还蕴藏了大量有价值的信息&#xff0c;可以通过日常的质检工作真正发现…

EtherCAT超高速实时运动控制卡XPCIE1032H上位机C#开发(一):驱动安装与建立连接

XPCIE1032H功能简介 XPCIE1032H是一款基于PCI Express的EtherCAT总线运动控制卡&#xff0c;可选6-64轴运动控制&#xff0c;支持多路高速数字输入输出&#xff0c;可轻松实现多轴同步控制和高速数据传输。 XPCIE1032H集成了强大的运动控制功能&#xff0c;结合MotionRT7运动…

OTA包添加自定义内容

起因 新开一条线&#xff0c;需要上传的OTA包里加点内容&#xff0c;好让后台校验它是否是当前这条线(短期最小改动)。 开整 之前看过ota包结构&#xff0c;整包和差分包里都有一个payload_properties.txt文件&#xff0c;所以最简单的就是给这个txt文件里追加点自定义内容&…

制造企业如何做好进销存管理工作?

本文你将了解&#xff1a;什么是进销存管理系统&#xff1f;国内制造信息化的发展现状如何&#xff1f;进销存管理系统的功能有哪些&#xff1f; 接下来搭建进销存管理系统教学中用到的图片和系统都来自简道云的进销存管理系统 这也是我们公司目前正在用的进销存管理系统&…

el-form-item的label的长度单独改掉,用vue3样式穿透的写法,加上css选择器查找特定的id拿到元素

为了让这个会员卡号这几个字和下面的表格对齐&#xff0c;需要改el-form-item的label的长度 如果直接改el-form的label-width,那么所有的el-form-item的label都会改&#xff0c;我不希望这样 我希望只改第1个会员卡号的label长度 给这个el-form-item添加一个id :deep(.el-for…

vnpy_ctp源码下载后转变为python可用的处理过程

目录 写在前面 下载源码并解压 创建python项目 环境 过程 编译vnpy_ctp源码 验证可用性 写在前面 window系统中必须安装有Visual Studio ,后面源码安装时需要进行C编译 下载源码并解压 GitHub - vnpy/vnpy_ctp: VeighNa框架的CTP交易接口 下载zip压缩包 解压 要在…

Mysql数据备份 —Navicat

一 Navicat 对于表的备份 根据自己的需求来选择 可以直接备份数据表 操作简单明了 二 Navicat 对于库的备份 对于数据库 直接通过转储SQL文件 保存结构和数据 需要新创建数据库 字符集和编码格式要对应 否则容易乱码 运行刚才生成的文件即可

【iOS开发】iOS App的加固保护原理:使用ipaguard混淆加固

​ 摘要 在开发iOS应用时&#xff0c;保护应用程序的安全是非常重要的。本文将介绍一种使用ipaguard混淆加固的方法来保护iOS应用的安全。通过字符串混淆、类名和方法名混淆、程序结构混淆加密以及反调试、反注入等主动保护策略&#xff0c;可以有效地保护应用程序的安全性。 …

案例精选|聚铭综合日志分析系统为中电飞华业务数据安全保驾护航

当下&#xff0c;云和网正从过去的独立走向融合&#xff0c;各行各业从“上网”纷纷演进到“上云”。“上云&#xff0c;才能更好地拥抱数字时代”。云网融合高质量发展对信息基础设施能力提出了新要求&#xff0c;同时运营商在产业数字化领域的业务探索也需要强大的云网能力支…

安科瑞故障电弧探测器在建筑电气的设计与应用

安科瑞 崔丽洁 【摘要】&#xff1a;电气设备是建筑中不可缺少的一部分&#xff0c;具有较为重要的作用和意义&#xff0c;在应用过程中不仅能够提升建筑本身实用性能&#xff0c;而且可为消费者提供更加优良的生活环境。但设备一旦在运行过程中出现故障&#xff0c;不仅会影响…

ros1 基础学习10 -全局字典参数的定义,获取,改值

全局字典参数的定义&#xff0c;获取&#xff0c;改值 一、参数模型二、 创建功能包三、参数命令行的使用(rosparam)四、使用程序来使用参数&#xff08;C&#xff09;4.1创建代码4.2编译4.3 编译文件 测试 在ROS Master中&#xff0c;存在一个参数服务器&#xff08;Parameter…

数学函数3D可视化工具来了

前面已经跟大家分享了两个数学可视化工具&#xff0c;manim和geogebra。 按理讲不应该再重复推荐类似的工具了&#xff0c;感觉犯了软件开发常说的忌讳&#xff1a;重复造轮子。 有的人也会抱怨手里的工具多了都不知道用哪一个了。 但遇到好用的工具又忍不住分享出来&#x…

04-SpringBoot的基础配置及其配置文件分类,解决Yaml文件失效问题

SpringBoot的配置 SpringBoot是用来提高Spring程序的开发效率的,使用SpringBoot后几乎不用做任何配置功能就有了,因为很多功能已经有默认配置帮我们做好了 配置文件的相关配置 在一个项目中不同的技术对应不同的配置文件并且这些配置文件的格式也不统一 SpringBoot提供了一…

Linux之IPC通信共享内存(一次拷贝)与消息队列、管道、信号量、socket(两次拷贝)总结(六十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…