c++中的链表list的模拟实现

拖更了半个月,我终于来填c++的坑啦。上次我们说的vetcor不知道小伙伴还记得多少呢?今天我们要讲list的模拟实现。

目录

  • 架构
    • 结点
    • list表的结构
  • 构造函数
  • 尾插push_back()
  • 尾删pop_back()
  • 计算个数:size()
  • 判断空empty()
  • ※迭代器问题
    • 普通迭代器
      • 迭代器主体
      • begin()和end()
      • operator*()
      • 前置++ operator++()
      • 后置++ operator++(int)
      • 前置-- operator--()
      • 后置-- operator--(int)
      • 重载不等于 operator!=(const Self& rhs)
      • 重载箭头 operator->()
    • const迭代器问题
      • 解决方法一:创建新的类。
      • 解决方法二:模板
  • 在某个位置前插入 insert()
  • 删除某位置的值 erase()
  • 清空链表 clear()
  • 拷贝构造
  • 析构函数
  • 问题
  • 实现整体代码

架构

我们之前也写过c语言版本的链表,当时很麻烦,还要传二级指针。但是现在不用了引用就能解决。我们先把大体的架构搭出来。

结点

在这里插入图片描述

list是一个个的结点指针链接,而且我们看手册的时候,会发现ist是一个双向带头的链表。所以我们先写节点的代码加粗样式

template<class T>
struct ListNode
{
//为什么要用struct?其实用class也ok,但是要整个public,因为节点我希望你可以随时申请,所以不能弄成私有。ListNode(const T& val=T()):_prev(nullptr),_next(nullptr),data(val){}ListNode<T>* _prev;//为什么是ListNode<T>*的指针呢?因为我们将来要指向//ListNode<T>类型的节点。ListNode<T>* _next;T data;
};

list表的结构

我们也说了是双向带头链表,所以list一定要有头结点。

template<class T>class list{public:typedef ListNode<T> Node;private:Node* _head;//头结点是一个节点指针类型。size_t _size;//因为库中有size这个接口,这里存一个会方便一点。};

构造函数

在我们之前实现过得带头双向链表,在刚开始的时候,就是对头结点进行处理,让头结点的前后指针指向自己。

	list(){_head = new Node;//给头一个空间_head->_prev = _head;_head->_next = _head;_size = 0;}

尾插push_back()

尾插,我们很熟悉了。就是先new出一个节点的大小,然后把这个节点连接到链表的尾部。如图:
在这里插入图片描述

void push_back(const T& val)
{Node* node = new Node(val);//申请节点Node* tail = _head->_prev;//如何找到4?是_head->prev!保存起来方便我们使用。node->_prev = tail;//链接node->_next = _head;tail->_next = node;_head->_prev = node;_size++;//修改size
}

尾删pop_back()

尾删也是老朋友了。话不多说如图:
在这里插入图片描述
简单叙述过程:就是先找到尾巴,然后保存一起来(方便我们删除)。然后将指针链接到尾删的前一个值。

void pop_back()
{Node* tail = _head->_prev;//保存_head->_prev = tail->_prev;//更改指向tail->_prev->_next = _head;delete tail;//删除节点_size--;//别忘了修改个数
}

计算个数:size()

这个就很简单了,因为我们内置了个数,所以直接返回就好了。

size_t size()
{return _size;
}

判断空empty()

bool empty()
{return _size == 0;
}

※迭代器问题

链表的迭代器很麻烦,不像vector那样用下标访问就可以的我们先把迭代器的主体写出来,分析一下,我们都需要什么吧。

bit::list<int>::const it = lt2.begin();
while (it != lt2.end())
{std::cout << *it << " ";it++;
}

我们平时访问数据的时候就这些,例如:迭代器本身、*星号、begin()、end()、自增++。那怎么写呢?

普通迭代器

问题:迭代器本身怎么写,还用T* 吗?写在list中码?(一定要思考这个问题!)


答案:不不不,我问你的自增,你在增谁?增加的list还是迭代器?我们要写operator++(),你一定要明白是谁在++。如果你把迭代器写到list中,你operator++()自增的是谁?一定要把这个问题想清楚。

自增的迭代器本身。那么我们的自增就不能写到list中,也就表示迭代器不能写在list中。那么我们就需要另外写一个类封装了。OK!开写!

迭代器主体

我们要遍历这个list链表的时候,我们一定要有这个地方的节点指针,我们才能访问这里的数据对吧,所以里面的成员变量是Node* _node;

template<class T>
class List_Iterator
{
public:typedef ListNode<T> Node;Node* _node;List_Iterator(Node* node):_node(node){}
};

begin()和end()

begin()和end()多好写,begin()是返回数据的开始和end()返回头结点(有效数据的下一个)。

问题:你写在哪?list类?还是iterator类?
答:思考一下,你的数据在哪?你的数据在list 里面对吧,我要访问遍历数据,我去哪里拿数据?list中,那么你的begin()和end()一定不能写在iterator。begin()和end()应该写在list中。

template<class T>class list{public:typedef ListNode<T> Node;iterator begin(){//这里存在单参数构造函数的隐式类型转换//按道理 return iterator(_head->next);//应该是这样的,匿名对象,但是单参数的构造函数可以进行隐式类型转换。return _head->_next;}iterator end(){return _head;}private:Node* _head;//头结点是一个节点指针类型。size_t _size;//因为库中有size这个接口,这里存一个会方便一点。};

operator*()

因为我们遍历一般都是要打印出它的值,所星号也是必不可少的。星号表示解压用。那么我们只需要返回当前节点的值就可以了。

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

前置++ operator++()

自增很简单,我们只需要下一个节点的指针就好,那么我们怎么找到下一个节点呢?是不是当前节点的_next?那就更简单了。

List_Iterator<T>& operator++()
{//this->_node= this->_node->_next;_node= _node->_next;return *this;
}

后置++ operator++(int)

后置++,就是我们创建一个临时变量,然后返回临时变量,但是让实际已经指向下一个节点了。注:不能返回引用,因为是临时变量。

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

前置-- operator–()

为什么要重载自减呢?是因为有些地方我们还是有需要的,所以就直写了。

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

后置-- operator–(int)

List_Iterator<T> operator--(int)
{Self tmp(*this);_node = _node->_prev;return tmp;
}

重载不等于 operator!=(const Self& rhs)

问题:在比较的时候,我们比的是节点指针还是数值呢?
答:节点指针!数值可能重复。

bool operator!=(const Self& rhs )
{return rhs._node != _node;
}

重载箭头 operator->()

为什么要重载operator->?我们一直用int内置类型来作为样例,这没有问题,但是我想问你,你的list只存int吗?没有自定义类型吗?如果我希望能存一个student类呢?可能你说(*it).成员变量也可以啊,但是你觉得方便吗?所以我们要重载->。

箭头的运算符重载:
在这里插入图片描述
从图上我们能发现,他隐藏了一个箭头,他应该是it.operator->()->_a1;但是事实上只有意见箭头,就表明了,他隐藏了一个箭头。

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

const迭代器问题

以上,我们用的都是普通的迭代器,那么const迭代器怎么写呢?你会说,我知道了!只要让他不改变不就好了?OK!说干就干。显示就是,依然改了,并没有用。
在这里插入图片描述
在这里插入图片描述

解决方法一:创建新的类。

问题就出在了你迭代器本身,你的返回值依然是普通的,我依然可以改。那你突然灵光一现,我在写一个const_ListIterator类就好了!好说干就干。你用了一下,真好用。但是你有没有发现代码很冗余?唯独只有operator*()和operator->()的返回值改变了,因为在拿取值的时候,我们要返回const类型的。

template<class T>
class List_ConstIterator
{
public:typedef ListNode<T> Node;typedef List_ConstIterator<T> Self;Node* _node;List_ConstIterator(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;}bool operator!=(const Self& rhs)  {return rhs._node != _node;}bool operator==(const Self& rhs){return _node == rhs._node;}const T& operator* (){return _node->data;}const T* operator->(){return &_node->data;}
};

解决方法二:模板

我们上面也说只是两个成员函数的返回类型不同,那么我们在处理返回类型不同的时候怎么做的呢?对!就是模板!模板是不是可以传不同类型呀?那么我们就只需要改一下。
在这里插入图片描述
list类中:
在这里插入图片描述

template<class T ,class Ref ,class Ptr>
class List_Iterator
{
public:typedef ListNode<T> Node;typedef List_Iterator< T, Ref, Ptr> Self;Node* _node;List_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;}bool operator!=(const Self& rhs ){return rhs._node != _node;}bool operator==(const Self& rhs){return _node == rhs._node;}Ptr operator->(){return &_node->data;}Ref operator* (){return _node->data;}
};

在某个位置前插入 insert()

库中给我们了三种,我们只实现第一种。需要迭代器类型的pos和 T val。刚好迭代器问题我们刚刚解决。

在这里插入图片描述
连接方式,如图:
在这里插入图片描述

iterator insert(iterator pos, const T& val)
{//我写的不美观Node* node = new Node(val);Node* tmp=pos._node->_prev;tmp->_next = node;node->_prev = tmp;node->_next = pos._node;pos._node->_prev = node;_size++;return _head;// Node* node = new Node(val);// Node* prev=pos->prev;// node->prev=prev;// node->next=pos;// pos->prev=node;// prev->next=node;// _size++;// return _head
}

删除某位置的值 erase()

这里我们要注意,库中要求返回被删除值的下一个的迭代器。
在这里插入图片描述
在这里插入图片描述

		iterator erase(iterator pos){//不美观版本assert(pos != end());//这里我担心有人有人恶意传迭代器,所以就判断了一下。Node* tmp = pos._node->_prev;tmp->_next = pos._node->_next;pos._node->_next->_prev = tmp;delete pos._node;_size--;return tmp->_next;/*美观版本Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return next;*/}

清空链表 clear()

注意,我们清空的链表,不需要清头指针,所以直接用迭代器,pop_back()就可以了。

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

拷贝构造

拷贝构造这里有几个问题。

问题1:直接push_back()可以吗?
答:不可以。你的头指针没有初始化,都是空,插入直接空指针访问。所以你需要初始化。
问题2:用我注释部分的迭代器可以吗?
答:不行,如果你传进来是一个const类型的迭代器呢?普通迭代器类型不匹配,除非你用auto。

//lt1(lt2)
list(const list<T>& lt)
{empty_init();//初始化头结点。for (auto& e : lt){push_back(e);}iterator it = lt.begin();类型不明确,用范围for更好//auto it = lt.begin();//这样也可以//while (it != lt.end())//{//	push_back(*it);//	it++;//}
}

析构函数

释放空间,并且还有头结点!!!有些小伙伴以为只用释放各个数据的节点就可以了,但是头结点也是你开出来的空间啊,所以也要记得释放。

~list()
{clear();//复用delete _head;
}

问题

如果我什么都写了,只有拷贝构造没有写,下面图代码对吗?
在这里插入图片描述

答:不对,因为传值传参回去调用拷贝构造,没有写拷贝构造就是浅拷贝,当函数结束 临时对象会被析构。但是浅拷贝导致本对象被析构,当整体结束的时候会导致二次析构。

实现整体代码

#pragma once
#include<iostream>
#include<assert.h>namespace bit {template<class T>struct ListNode{ListNode(const T& val=T()):_prev(nullptr),_next(nullptr),data(val){}ListNode<T>* _prev;//为什么是ListNode<T>*的指针呢?因为我们将来要指向//ListNode<T>类型的节点。ListNode<T>* _next;T data;};template<class T ,class Ref ,class Ptr>class List_Iterator{public:typedef ListNode<T> Node;typedef List_Iterator< T, Ref, Ptr> Self;Node* _node;List_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;}bool operator!=(const Self& rhs ){return rhs._node != _node;}Ref operator* (){return _node->data;}bool operator==(const Self& rhs){return _node == rhs._node;}Ptr operator->(){return &_node->data;}};//template<class T>//class List_ConstIterator//{//public://	typedef ListNode<T> Node;//	typedef List_ConstIterator<T> Self;//	Node* _node;//	List_ConstIterator(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;//	}//	bool operator!=(const Self& rhs)  //	{//		return rhs._node != _node;//	}//	const T& operator* ()//	{//		return _node->data;//	}//	bool operator==(const Self& rhs)//	{//		return _node == rhs._node;//	}//	const T* operator->()//	{//		return &_node->data;//	}//};template<class T>class list{public:typedef ListNode<T> Node;typedef List_Iterator<T, T&, T*> iterator;typedef List_Iterator<T, const T&, const T*> const_iterator;//typedef List_Iterator<T> iterator;//typedef List_ConstIterator<T> const_iterator;void empty_init(){_head = new Node;//给头一个空间_head->_prev = _head;_head->_next = _head;_size = 0;}list(){empty_init();//刚创建一个链表的时候,没有插入任何数据,我们需要让他指向自己}~list(){clear();delete _head;}//lt1(lt2)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}iterator it = lt.begin();类型不明确,用范围for更好//auto it = lt.begin();//这样也可以//while (it != lt.end())//{//	push_back(*it);//	it++;//}}void push_back(const T& val){/*Node* node = new Node(val);Node* tail = _head->_prev;node->_prev = tail;node->_next = _head;tail->_next = node;_head->_prev = node;*/insert(end(), val);}void pop_back(){/*Node* tail = _head->_prev;_head->_prev = tail->_prev;tail->_prev->_next = _head;delete tail;*/erase(--end());}//当我们想使用insert的时候,发现需要迭代器那么我们先实现迭代器,怎么做呢?iterator begin(){//这里存在单参数构造函数的隐式类型转换//return _head->_next;}iterator end(){return _head;}const_iterator begin()const{//这里存在单参数构造函数的隐式类型转换return _head->_next;}const_iterator end()const{return _head;}//目前测试的迭代器没有大碍,那么就写insertiterator insert(iterator pos, const T& val){//我写的不美观Node* node = new Node(val);Node* tmp=pos._node->_prev;tmp->_next = node;node->_prev = tmp;node->_next = pos._node;pos._node->_prev = node;_size++;return _head;// Node* node = new Node(val);// Node* prev=pos->prev;// node->prev=prev;// node->next=pos;// pos->prev=node;// prev->next=node;// _size++;// return _head}iterator erase(iterator pos){//我写的不美观assert(pos != end());Node* tmp = pos._node->_prev;tmp->_next = pos._node->_next;pos._node->_next->_prev = tmp;delete pos._node;_size--;return tmp->_next;//老师写的/*Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return next;*/}size_t size(){return _size;}bool empty(){return _size == 0;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}private:Node* _head;size_t _size;};
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue2[黑马笔记]

vue基础 是什么—javascript框架 构建用户界面的前端框架 1.构建用户界面用vue往html页面中填充数据 2.框架现成的解决方案&#xff0c;遵守框架的规范去实现自己的业务功能学习vue 就是学习vue框架中规定的用法vue的指令组件&#xff08;对ul结构的复用&#xff09;&#x…

windows服务启动提示‘服务没有响应控制功能’(mysql启动报错)

在安装mysql的时候&#xff0c;在windows服务项启动 或 使用命令net start mysql 时启动是报错&#xff0c;提示 服务没有响应控制功能 发生原因&#xff1a; Windows10 x64 或 更高的操作系统&#xff0c;有些系统缺少一些组件 解决办法&#xff1a; 1、下载最新的 Microsoft …

CSS详解(一)

1、css工作中使用场景 美化网页&#xff08;文字样式、背景样式、边框样式、盒子模型、定位、动画、&#xff09;&#xff0c;布局页面&#xff08;flex布局、响应式布局、媒体查询&#xff09; 2、CSS 规则 通常由两个主要部分组成选择器和样式声明 2.1选择器 选择器指定了…

制作自己的YOLOv8数据集

制作自己的YOLO8数据集 前言 该数据集的格式参照于coco数据集结构✨ 步骤一&#xff1a;收集图像数据 从互联网上下载公开的数据集&#xff0c;也可以使用摄像头或其他设备自行采集图像&#xff0c;确保你的图像数据覆盖了你感兴趣的目标和场景 步骤二&#xff1a;安装Labe…

正点原子[第二期]ARM(I.MX6U)裸机篇学习笔记-1.2

前言&#xff1a; 本文是来自哔哩哔哩网站上视频“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”的学习笔记&#xff0c;在这里会记录下正点原子Linux ARM MX6ULL 开发板根据配套的哔哩哔哩学习视频所作的实验和笔记内容。本文大量的引用了正点原子哔哔哩网…

[论文笔记] EcomGPT:COT扩充数据的电商大模型

社区供稿 | EcomGPT:基于任务链数据的电商大模型(附魔搭推理实践) - 知乎 https://arxiv.org/pdf/2312.15696.pdf EcomInstruct指令数据集构建 数据集组成 COT方式构造垂域训练数据:把原本的垂域任务分解成了原子任务,构造了基于解决原子任务的数据。这样能用类似…

网页模版如何用

现在的网页模版已经得到了许多人的喜爱和使用。随着人们对互联网的需求不断增加&#xff0c;更多的公司和组织需要拥有自己的网站&#xff0c;以推广他们的品牌和服务。而网页模版为他们提供了一个简单而高效的方法来创建自己的网站。 网页模版是预先设计好的网站模板&#xff…

人工智能技术在教育中的潜力有多大

原文&#xff1a;人工智能技术在教育中的潜力有多大&#xff1f; - 知乎 作者&#xff1a;大全Prompt 链接&#xff1a;https://www.zhihu.com/question/637034129/answer/3346272227 来源&#xff1a;知乎 谢邀&#xff1a;在技术快速发展的今天&#xff0c;人工智能&#x…

炒股自动化:券商官方,散户可用,查询订单状态API如何用?

券商官方的接口&#xff0c;个人账户可申请&#xff0c;入金门槛低&#xff0c;接入文档完善&#xff0c;技术支持好的&#xff0c;经过我们筛选后&#xff0c;只有一家符合 会编程&#xff0c;有基础&#xff0c;只是需要API接口的朋友不用看这些&#xff0c;不会写程序的朋友…

【vue2】实现微信截图(复制图片)在项目内可粘贴

需求 后台管理在上传图片地方需要将复制的图片粘贴上传 一、添加事件 在原有上传组件的基础上添加 paste事件 二、方法 onPaste(e) {const items (e.clipboardData || window.clipboardData).items;let blob null;for (let i 0; i < items.length; i) {if (items[i].ty…

设计模式:单例、原型和生成器

在这篇文章中&#xff0c;我们将重点介绍其余的创建模式&#xff1a;Singleton&#xff0c;Builder和Prototype。 在我看来&#xff0c;这些模式不如工厂重要。然而&#xff0c;了解它们仍然很有用。我将提供UML描述&#xff0c;简单的java示例&#xff08;这样即使你不了解jav…

[linux网络编程]UDP协议和TCP协议的使用

目录 看以下内容前&#xff0c;你要先了解main函数带参数有什么用、 了解socket的相关函数接口 如果不了解socket的相关函数接口请先看我这篇文章 main函数带参数有什么用 UDP udp_server 1.生成socket文件描述符 2.填充sockaddr_in信息 3.bind 4.发&#xff08;收&…

渗透之sql注入练气篇

sql注入产生的原因&#xff1a; 由于程序过滤不严谨&#xff0c;导致用户有一些异常输入&#xff0c;最终触发数据库的查询。所以会出现sql注入这个问题。有些恶意的人就会利用这些信息导致数据库泄露。 注意&#xff1a;一般我们存在注入点我们会查询管理员的账号和密码&#…

如何30天快速掌握键盘盲打

失业后在家备考公务员&#xff0c;发现了自己不正确的打字方式&#xff0c;决定每天抽出一点时间练习打字。在抖音上看到一些高手的飞速盲打键盘后&#xff0c;觉得使用正确的指法打字是很必要的。 练习打字&#xff0c;掌握正确的键盘指法十分关键。 练习打字的第一步是找到…

ElasticSearch批处理

在刚才的新增当中&#xff0c;我们是一次新增一条数据。那么如果你将来的数据库里有数千上万的数据&#xff0c;你一次新增一个&#xff0c;那得多麻烦。所以我们还要学习一下批量导入功能。 也就是说批量的把数据库的数据写入索引库。那这里的需求是&#xff0c;首先利用mybat…

C++中布隆过滤器

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

【单链表专题】

单链表专题 1.引入2.链表2.1链表的关系2.2链表的结构 3.代码实现链表 1.引入 对于顺序表而言 中间/头部的插⼊删除&#xff0c;时间复杂度为O(N)增容需要申请新空间&#xff0c;拷⻉数据&#xff0c;释放旧空间。会有不小的消耗。增容⼀般是呈2倍的增⻓&#xff0c;势必会有⼀…

Github进行fork后如何与原仓库同步[解决git clone 太慢的问题]

前言 fork了一个仓库以后怎么同步源仓库的代码&#xff1f; 先说一下git clone太慢的问题&#xff0c;可以通过代理拉取代码&#xff0c;具体请看&#xff1a; https://gitclone.com/ 步骤 1、执行命令 git remote -v 查看你的远程仓库的路径。 以一个实际例子说明&#x…

[Spring Cloud] (4)搭建Vue2与网关、微服务通信并配置跨域

文章目录 前言gatway网关跨域配置取消微服务跨域配置 创建vue2项目准备一个原始vue2项目安装vue-router创建路由vue.config.js配置修改App.vue修改 添加接口访问安装axios创建request.js创建index.js创建InfoApi.js main.jssecurityUtils.js 前端登录界面登录消息提示框 最终效…

微信小程序使用echarts组件实现饼状统计图功能

微信小程序使用echarts组件实现饼状统计图功能 使用echarts实现在微信小程序中统计图的功能&#xff0c;具体的实现步骤思路可进我主页查看我的另一篇博文https://blog.csdn.net/weixin_45465881/article/details/138171153进行查看&#xff0c;本篇文章主要使用echarts组件实…