C++初阶之list的使用和模拟以及反向迭代器的模拟实现

news2024/11/24 20:54:18

个人主页:点我进入主页

专栏分类:C语言初阶  C语言进阶  数据结构初阶    Linux    C++初阶    算法

欢迎大家点赞,评论,收藏。

一起努力,一起奔赴大厂

一.list简介

        list是一个带头双向链表,在数据结构的时候,我写过关于带头双向循环链表的实现,可以看博客https://yangking.blog.csdn.net/article/details/134495668,我们可以看下面的图,是list的存储结构,

本次的内容包括list的使用,list的模拟实现,list的迭代器以及反向迭代器的原理,模拟实现和使用,最重要的是迭代器和反向迭代器的内容,在前面string和vector中迭代器是原生的指针,但是在这里不是,具体是什么样子的我们可以看后面的内容。 

二.一个简易的list

2.1.单个节点

template<class T>
struct ListNode
{
	typedef  ListNode<T>  Node;
		
	Node* _next;
	Node* _prev;
	T _data;
	ListNode(const T& val = T())
		: _next(nullptr)
		, _prev(nullptr)
		, _data(val)
	{}
};

在这里我们可以使用strcut结构体来创建一个节点

2.2.默认构造

void empty_init()
{
	_head = new Node;
	_head->_next = _head;
	_head->_prev = _head;
	_size = 0;
}
list()
{
	empty_init();
}

我们创建一个头节点, 让它指向自己。

2.3push_back

void push_back(const T& val)
{
	Node* newnode = new Node(val);
	Node* prev = _head._node->_prev;
	Node* cur = _head._node;

	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	_size++;
}

在这里我们就是简单的插入操作,不给具体的解释。到这里我们简易的list就完成了,我们依次插入1234可以看到

void test()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
}

三.迭代器

        我们封装一个迭代器的类,我们的迭代器需要支持

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

所以我们里面需要包括!=,*it,++it,以及begin和end这些内容,我们需要知道迭代器模仿的是Node*这个行为,我们看一下模拟代码:

template <class T>
struct ListIterator
{
	typedef ListNode<T>  Node;
	typedef ListIterator<T> Self;
	Node* _node;
	ListIterator(Node* node)
		:_node(node)
	{}
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}


	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	Self operator--(int)
	{
		Self tmp(_node);
		_node = _node->_prev;
		return *tmp;
	}
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	T& operator*()
	{
		return _node->_data;
	}
};

我们在list类里面加入

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

我们运行测试代码

void test()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	list<int>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

运行结果为

四.insert和erase以及复用

4.1insert

 在这里我们的insert用迭代器进行插入,给以个位置,插入它的前面,

 我们的代码如下:

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

	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	_size++;
}

有了我们的insert我们的push_back可以复用我们的insert,可以改为

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

在这里我们的insert不会造成迭代器失效,因为指针指向的位置不会发生改变,它是插入到pos位置的前面。

4.2erase以及迭代器失效

对于返回值为什么是iterator,这和我们的迭代器失效有关,我们先用void来展示其中的问题。

void erase(iterator pos)
{
	Node* prev = pos._node->_prev;
	Node* next = pos._node->_next;

	delete pos._node;
	prev->_next = next;
	next->_prev = prev;
	_size--;
}

我们的测试代码如下:
 

void test()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	list<int>::iterator it = l.begin();
	while (it != l.end())
	{
		if (*it % 2 == 0) l.erase(it);
		else it++;
	}
	it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

在这里会出现错误,原因是迭代器失效,野指针的引用,所以我们需要对迭代器进行维护,所以有了返回值,我们改为

iterator erase(iterator pos)
{
	Node* prev = pos._node->_prev;
	Node* next = pos._node->_next;

	delete pos._node;
	prev->_next = next;
	next->_prev = prev;
	_size--;
	return next;
}

4.3复用

有了这两个,我们的pop_back,pop_front,push_front就可以有了

void pop_back()
{
	erase(--end());
}
void pop_front()
{
	erase(begin());
}
void push_front(const T& val)
{
	insert(begin(), val);
}

五.operator->()

        我们的T是一个结构体

struct A
{
	int a = 1;
	int b = 2;
};
void test()
{
	list<A> l;
	l.push_back({ 1,2 });
	l.push_back({ 2,3 });
	l.push_back({ 3,4 });
	l.push_back({ 4,5 });
	list<A>::iterator it = l.begin();

	while (it != l.end())
	{
	/*	cout << (*it).a << " " << (*it).b << endl;*/
		cout << it._node->_data.a << " " << it._node->_data.b << endl;
		it++;
	}
}

 我们的解引用是不是看的非常难受,所有我们需要operator一下,

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

 我们改为

cout << it->a << " " << it->b << endl;

六.const迭代器

        有了我们的非const版本的,那么我们就需要我们的const版本的,const版本就是将所有的都复制一份,然后operator*返回const的

template <class T>
struct ListConstIterator
{
	typedef ListNode<T>  Node;
	typedef ListConstIterator<T> Self;
	Node* _node;
	ListConstIterator(Node* node)
		:_node(node)
	{}
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}


	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	Self operator--(int)
	{
		Self tmp(_node);
		_node = _node->_prev;
		return *tmp;
	}
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	const T& operator*()
	{
		return _node->_data;
	}
	const T* operator->()
	{
		return &_node->_data;
	}
};

由于这个会和非fonst很多内容一样,会造成代码冗余,所以我们可以利用模板来简化我们的代码,我们非const的代码函数的返回值为Self,T&,T*,const版本的是Self,const T&,const T*,所以我们可以写成模板

template <class T,class Ref,class Ptr>
struct ListIterator
{
	typedef ListNode<T>  Node;
	typedef ListIterator< T, Ref, Ptr> Self;
	Node* _node;
	ListIterator(Node* node)
		:_node(node)
	{}
	bool operator==(const Self& it)
	{
		return _node == it._node;
	}
	bool operator!=(const Self& it)
	{
		return _node != it._node;
	}


	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}
	Self operator--(int)
	{
		Self tmp(_node);
		_node = _node->_prev;
		return *tmp;
	}
	Self& operator++()
	{
		_node = _node->_next;
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(_node);
		_node = _node->_next;
		return tmp;
	}
	Ref operator*()
	{
		return _node->_data;
	}
	Ptr operator->()
	{
		return &_node->_data;
	}
};

我们在list类里面写入

typedef ListIterator<T,T&,T*> iterator;
typedef ListIterator<T,const T&,const T*> const_iterator;

这样我们由我们自己写改为了编译器去写。

七.反向迭代器

        我们的反向得带起是使用正向迭代器进行写的,它的rbegin是正向迭代器的end,rend是正向迭代器的begin,所以它的operator*是解引用前一个的。

template <class Iterator, class Ref, class Ptr>
struct ReverseIterator
{
	typedef ReverseIterator<Iterator, Ref, Ptr> Self;

	Iterator _it;
		
	ReverseIterator(Iterator it)
		:_it(it)
	{}
	Ref operator*()
	{
		Iterator tmp = _it;
		--tmp;
		return *tmp;
	}
	Ptr operator->()
	{
		return _it.operator->();
	}

	Self rbegin()
	{
		return ReverseIterator(_it.end());
	}
	Self rend()
	{
		return ReverseIterator(_it.begin());
	}
	Self operator++()
	{
		--_it;
		return *this;
	}
	Self operator++(int)
	{
		Iterator tmp = _it;
		--_it;
		return tmp;
	}
	Self operator--()
	{
		++_it;
		return *this;
	}
	Self operator--(int)
	{
		Iterator tmp = _it;
		tmp = _it;
		++_it;
		return tmp;
	}
	bool operator!=(Self it)
	{
		return _it != it._it;
	}

};

到这里我们的内容结束了。

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

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

相关文章

如何通过专业的服务器同步软件,降低IT人员的运维管理压力?

随着企业结构分散化的不断扩大&#xff0c;企业内部和企业间的信息互动更加频繁。越来越多的企业要求内部各种业务数据在服务器、数据中心甚至云上能够有实时的同步留存。所以&#xff0c;企业需要服务器同步软件&#xff0c;通过在两个或更多设备之间同步数据并自动更新更改来…

python软件开发遇到的坑-相对路径文件读写异常,不稳定

1. os.chdir()会影响那些使用相对路径读写文件的程序,使其变得不稳定,默认情况下,当前工作目录是主程序所在目录,使用os.chdir会将当前工作目录修改到其他路径。 资料: python相对路径写对了却报错是什么原因呢? - 知乎 (zhihu.com) 相对文件路径 : r/learnpython (red…

计算机系列之知识产权和标准化

16、知识产权和标准化 ◆知识产权是指公民、法人、非法人单位对自己的创造性智力成果和其他科技成果依法享有的民事权。是智力成果的创造人依法享有的权利和在生产经营活动中标记所有人依法所享有的权利的总称。包含著作权、专利权、商标权、商业秘密权、植物新品种权、集成电…

流畅的python-学习笔记_对象引用、可变性、垃圾回收

变量不是盒子 即变量是引用&#xff0c;而不是实际内存&#xff0c;多个标识赋值相同变量时&#xff0c;多余标识是引用 标识、相等性、别名 比较对象的值&#xff0c;is比较对象的id。实际调用对象的__eq__方法。is速度比快&#xff0c;因为is不能重载&#xff0c;省去了寻…

企业微信和钉钉接口打通对接实战

企业微信和钉钉接口打通对接实战 数据源系统:企业微信 企业微信是腾讯微信团队打造的企业通讯与办公工具&#xff0c;具有与微信一致的沟通体验&#xff0c;丰富的OA应用&#xff0c;和连接微信生态的能力&#xff0c;可帮助企业连接内部、连接生态伙伴、连接消费者。专业协作、…

基于STC12C5A60S2系列1T 8051单片机的IIC通信的0.96寸4针OLED12864显示大小写英文的应用

基于STC12C5A60S2系列1T 8051单片机的IIC通信的0.96寸4针OLED12864显示大小写英文的应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器OLED12864简单介绍一…

leetcode295. 数据流的中位数

class MedianFinder {//A为小根堆&#xff0c;B为大根堆List<Integer> A,B;public MedianFinder() {A new ArrayList<Integer>();B new ArrayList<Integer>();}public void addNum(int num) {int m A.size(),n B.size();if(m n){insert(B,num);int top …

2024年第十三届工程与创新材料国际会议(ICEIM 2024)即将召开!

2024年第十三届工程与创新材料国际会议&#xff08;ICEIM 2024&#xff09;将于2024年9月6-8日在日本东京举行。ICEIM 2024由东京电机大学主办&#xff0c;会议旨在材料科学与工程、材料特性、测量方法和应用等相关领域进行学术交流与合作&#xff0c;在材料的微观世界里&#…

大坝安全监测站

TH-WY1 GNSS位移监测站在大坝安全监测中的应用具有重要意义。通过实时监测、高精度测量、高度自动化和多种数据融合等技术手段&#xff0c;GNSS位移监测站为大坝安全监测提供了强有力的支持。下面为大家介绍GNSS位移监测的作用&#xff1a; 实时监测&#xff1a;GNSS位移监测站…

Vitis HLS 学习笔记--AXI_STREAM_TO_MASTER

目录 1. 简介 2. 示例 2.1 示例功能介绍 2.2 示例代码 2.3 顶层函数解释 2.4 综合报告&#xff08;HW Interfaces&#xff09; 2.5 关于TKEEP和TSTRB 2.6 综合报告&#xff08;SW I/O Information&#xff09; 3. 总结 1. 简介 本文通过“<Examples>/Interface…

Vue 插槽

Vue插槽是一种特殊的语法&#xff0c;用于在组件中定义可复用的模板部分。它允许开发者在组件的标记中声明一个或多个插槽&#xff0c;然后在使用该组件时&#xff0c;可以根据自己的需求将内容插入到这些插槽中。 Vue插槽分为默认插槽和具名插槽两种。 默认插槽 语法 组件…

PON网络和HFC网络

目录 1.概念 2.分类 3.重点 1.概念 PON PON是一种典型的无源光纤网络&#xff0c;是一种点到多点的无源光纤接入技术。 是指 (光配线网中) 不含有任何电子器件及电子电源&#xff0c;ODN全部由光分路器 (Splitter) 等无源器件组成&#xff0c;不需要贵重的有源电子设备。一个…

pyqt标签常用qss格式设置

pyqt标签常用qss格式设置 QSS介绍标签常用的QSS设置效果代码 QSS介绍 Qt Style Sheets (QSS) 是 Qt 框架中用于定制应用程序界面样式的一种语言。它类似于网页开发中的 CSS&#xff08;Cascading Style Sheets&#xff09;&#xff0c;但专门为 Qt 应用程序设计。使用 QSS&…

iPhone查看本机号码只需要这3招,不再为号码忘记犯愁!

在日常生活中&#xff0c;我们经常需要使用手机号码进行各种通讯活动&#xff0c;但有时候会忘记自己的手机号码&#xff0c;让人感到非常尴尬。不过&#xff0c;如果您是iPhone用户&#xff0c;那么您可以放心了&#xff01;因为在iphone查看本机号码只需要简单的几个步骤&…

44 网络基础

本章重点 了解网络发展背景&#xff0c;对局域网/广域网的概念有基本认识 了解网络协议的意义&#xff0c;重点理解TCP/IP五层结构模型 学习网络传输的基本流程&#xff0c;理解封装和分用 目录 1.网络发展 2.协议 3.OSI七层模型 4.TCP/IP五层模型 5.网络传输流程图 6.网络中…

一跨8 双跨9的电机讲解

前面讲解的都是一路串联的电机&#xff0c;下面讲解的是一个 &#xff0c;里面有一组线圈和2组线圈同时存在的一个电机。 1.有多少组线圈 ------- 12组 2.每组线圈有几把线 -------- 1 2 1 2 1 2 1 2 1 2 1 2 共12把。 3.跨距 单个线圈跨8 双组线圈 跨 9 4.匝数…

聚观早报 | vivo X100S外观公布;蔚来乐道L60曝光

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 5月7日消息 vivo X100S外观公布 蔚来乐道L60曝光 iPhone17将推Slim机型 2024年五一档电影票房 索尼Xperia 1 VI…

Redis 实战之事务的实现

事务的实现 事务开始命令入队事务队列执行事务总结 一个事务从开始到结束通常会经历以下三个阶段&#xff1a; 1、 事务开始&#xff1b; 2、 命令入队&#xff1b; 3、事务执行。 本节接下来的内容将对这三个阶段进行介绍&#xff0c; 说明一个事务从开始到结束的整个过程。 …

uniapp使用iconfont

1、把这两个文件在项目的静态资源目录下 2、修改iconfont.css文件 3、最后在app.vue中引入

一起深度学习(AlexNet网络)

AlexNet神经网络 代码实现&#xff1a; 代码实现&#xff1a; import torch from torch import nn from d2l import torch as d2lnet nn.Sequential(# 采用了11*11的卷积核来捕捉对象&#xff0c;因为原始输入数据比较大#步幅为4 &#xff0c;可减少输出的高度核宽度。#输出通…