【STL】模拟实现list

目录

list需要实现的接口

结点类的创建

迭代器类的实现

构造函数

++运算符的重载

--运算符的重载

!=运算符重载和==运算符重载

operator*

operator->

list的模拟实现

构造函数

拷贝构造函数

 赋值运算符重载函数

析构函数

迭代器相关函数

begin和end

front和back

push_front和push_back   

pop_front和pop_back

其他函数

size

clear

empty

swap


list需要实现的接口

namespace cl
{//模拟实现list当中的结点类template<class T>struct _list_node{//成员函数_list_node(const T& val = T()); //构造函数//成员变量T _val;                 //数据域_list_node<T>* _next;   //后继指针_list_node<T>* _prev;   //前驱指针};//模拟实现list迭代器template<class T, class Ref, class Ptr>struct _list_iterator{typedef _list_node<T> Node;typedef _list_iterator<T, Ref, Ptr> self;_list_iterator(node* node);  //构造函数//各种运算符重载函数self operator++();self operator--();self operator++(int);self operator--(int);bool operator==(const self& s) const;bool operator!=(const self& s) const;Ref operator*();Ptr operator->();//成员变量Node* _node; //一个指向结点的指针};//模拟实现listtemplate<class T>class list{public:typedef _list_node<T> Node;typedef _list_iterator<T, T&, T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;//默认成员函数list();list(const list<T>& lt);list<T>& operator=(const list<T>& lt);~list();//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//访问容器相关函数T& front();T& back();const T& front() const;const T& back() const;//插入、删除函数void insert(iterator pos, const T& x);iterator erase(iterator pos);void push_back(const T& x);void pop_back();void push_front(const T& x);void pop_front();//其他函数size_t size() const;void clear();bool empty() const;void swap(list<T>& lt);private:Node* _head; //指向链表头结点的指针size_t _szie;};
}

结点类的模拟实现:

list在底层中,就是以带头双向循环链表实现的

所以实现list我们首先的创建一个结点类。

该结点类需要三个成员变量:

1. 数据

2. 上一个结点的指针

3.下一个结点的指针

结点类的创建

_list_node

template<class T>
struct list_node
{list_node* _next;list_node* _prev;T _val;list_node(list_node<T> val = T()):_next(nullptr),_prev(nullptr),_val(val){}
};

在结点类中我们需要一个构造函数来帮我们完成结点以及数据的初始化,因为结点类需要被_list_iterator和list使用,所以这里我们使用struct

迭代器类的实现

之前string和vector我们都没说需要实现迭代器类,为什么list需要实现一个迭代器类呢?

因为string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针的自增自减以及解引用操作等一系列操作,就可以对相应位置的数据进行操作,所以string和vector当中的迭代器就是原生指针

对于list来说,它的构成并不是一块连续的空间,它的结点在内存中的位置是随机的,我们不能通过结点指针的自增自减以及解引用操作对相应结点的数据进行操作

迭代器的意义是,让使用者不必在意容器的底层实现原理,可以用简单统一的方式对容器内数据进行访问

因为迭代器·结点指针的行为不能满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符进行重载,使得我们可以像使用string和vector当中的迭代器一样去使用list当中的迭代器

迭代器类的模板参数说明

_list_iterator<class T, class Ref, class Ptr>

在list类中,我们typedef了两种类型的模板类,分别实现的是const 迭代器和非 const 迭代器

typedef _list_iterator<T, T&, T*> iterator
typedef _list_iterator<T, const T&, const T*> const_ierator

从这里就可以看出,Ref是引用类型,Ptr是指针类型

当我们使用不同迭代器时编译器就会根据模板来实例化出一个普通迭代器,当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象

若没有设计这三种模板参数,那么就不能很好的区分普通迭代器和const迭代器

构造函数

迭代器类实际上就是对结点指针进行了封装,成员变量只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造出一个迭代器对象即可

_list_iterator(Node* node):_node(ndoe)
{}

++运算符的重载

前置++,作用是让结点指针指向后面一个位置,然后返回自增的结点指针即可

self& operator++()
{_node = _node->next;return *this;
}

对于后置++,我们需要记录结点指针当前的位置,让结点指针向指向后一个结点,然后返回自增前的结点指针即可

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

注意:self是当前迭代器对象的类型:

_list_iterator<class T, class Ref, class Ptr>

--运算符的重载

前置--,作用是让结点指针指向前面一个位置,然后返回自减的结点指针即可

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

对于后置--,我们需要记录结点指针当前的位置,让结点指针向指向前一个结点,然后返回自减前的结点指针即可

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

!=运算符重载和==运算符重载

使用!= , ==运算符比较两个迭代器的时候,其实我们比较的是两个迭代器是否指向相同的结点指针,或者指向不同的结点指针

bool operator==(const self& it) const
{return _node == it._node
}bool operator!=(const self& it) const
{return _node != it._node
}

operator*

当我们使用解引用操作符时,是想得到该位置的数据内容,所以我们直接返回当前结点指针所指向的数据即可,但是这里需要解引用返回,得到数据之后,我们后面可能会对这个数据进行修改

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

operator->

指向运算符重载我们只需要返回数据的地址就可以了

Ptr operator->()  
{return &_node->val
}

但是看到这里,你可能觉得有点问题

这里应该是两个箭头去访问it,第一个->调用指向运算符重载函数,返回一个指针,然后再通过指针加指向运算符去访问对象当中的变量

但是一个地方出现两个箭头可读性太差了,为了增加可读性,编译器做了优化处理,优化了一个箭头

list的模拟实现

list是一个带头双向循环链表

构造函数

将初始化操作进行封装,后续会多次使用到,创建一个指针结点_head,并且让前驱指针和后驱指针指向自己即可完成初始化

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

拷贝构造函数

 创建一个新头结点_head,使前驱指针和后驱指针指向自己,遍历lt对象,将lt的数据通过尾插的方式给新的容器

list(const list<T>& lt)
{empty_init();//这里加引用提高效率,减少拷贝//加const是防止e不被篡改for (const auto& e : lt){push_back(e);}
}

 赋值运算符重载函数

传统写法:

		list<T>& opertor=(const list<T>& lt){if (*this != &lt){clear();//清空容器for (const auto& e : lt){push_back(e);}}return *this;}

现代写法:

这个写法代码量较少,首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数生成一个临时对象,使用这个临时对象给新容器进行交换即可完成操作,结束之后lt会调用析构函数销毁

//现代写法:
list<T>& opertor = (list<T> lt)
{swap(lt);return *this;
}

析构函数

使用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空

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

迭代器相关函数

begin和end

begin()迭代器指向第一个元素,end()迭代器指向末尾元素的后一个元素

iterator begin()
{//隐式类型转换// return _head;//强制类型转换return iterator(_head->_next);
}iterator end()
{//隐式类型转换return _head;//强制类型转换//return iterator(_head);
}

以上两种方式都可以

第一种是Node*类型对iterator的强制类型转换

第二种是Node*类型对itreator的隐式类型转换

还有const类型的迭代器

const_iterator end() const
{//隐式类型转换// return _head->prev;//强制类型转换return iterator(_head);
}const_iterator begin() const
{//隐式类型转换// return _head;//强制类型转换return iterator(_head->_next);
}

front和back

//获取尾部元素
T& back() const
{return *(end() - 1);
}
//获取头部元素
T& front() const
{return *begin();
}//获取尾部元素
const T& back() const
{return *(end() - 1);
}
//获取头部元素
const T& front() const
{return *begin();
}

push_front和push_back   

pop_front和pop_back

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

插入,删除函数

insert

pos位置前插入新结点

先通过所给迭代器得到该位置出的结点指针cur,然后通过cur指针找到前一个位置的结点指针prev

然后根据所给val的值建立一个新结点指针,然后实现和prve,cur的双向连接连接

iterator insert(iterator pos, const T& val)
{//在pos位置前插入,保存pos前面的地址,以防找不到Node* cur = pos->_node;Node* prev = cur->_prev;Node* newnode = new Node(val);cur->_prev = newnode;newnode->_next = cur;newnode->_prev = prev;prev ->_next  = newnode;++_size;//返回插入结点的地址,更新迭代器,以免迭代器失效return newnode;
}

erase

先根据所给迭代器得到该位置处的结点指针cur,通过cur指针找到前一个位置的结点prev和后一个位置的结点next,然后释放cur,最后将prev与next两个结点连接起来

iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._nodeNode* Prev = cur->prev;Node* Next = cur->next;Prev->next = next;Next->_prev = prev;delete cur;--_size;return pos;
}

其他函数

size

这里size的设计是,创建_size成员,使用增加结点的函数(例:push_back insert时)时,对_size进行++处理,进行删除操作时_size做--操作,即_size就是当前结点个数

size_t size() const
{return _size;
}

clear

清理链表,删除所有结点,erase涉及迭代器失效问题,这里的处理方案是,让erase返回下一个结点的指针,不断去更新lt.

clear这里不能给clear加const,加上const了,就代表给我需要删除的对象加上了const,*this就不能进行删除操作了

	void clear(){iterator it = begin();while (it != end()){//接收下一个位置的指针,避免迭代器失效it = erase(it);}_size = 0;}

empty

判断链表是否为空

bool empty()
{return begin() == end();
}

swap

我们访问的是标准库里面的swap,所有需要再swap前面加上::(域作用限定符)

void swap(list<T>& lt)
{::swap(_node, lt._node);::swap(_size, lt._size);
}

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

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

相关文章

【超详细】TCP协议

TCP(Transmission Control Protocol 传输控制协议) 传输层协议有连接可靠传输面向字节流 为什么TCP是传输控制协议呢&#xff1f; 我们以前所看到的write接口&#xff0c;都是把用户级缓冲区的数据拷贝到发送缓冲区中&#xff0c;然后数据就由TCP自主决定了&#xff0c;所以…

crashrpt3 开源项目的Vs 2022 C++20及其以上的编译

1. 首先从github 下载源代码 crashrpt3 2. 用CMake Gui 编译成vs studio 工程文件 2.1 点击 config 按钮 2.2 依次点击 Generate 按钮、Open Project 按钮.之后vs 2022 会打开编译好的sln工程文件 3.全选解决方案里面的所有项目,设置C语言标准,我这里设置是最新C,即启用的是…

【文档智能】文本文字识别、公式识别、表格文字识别核心算法及思路及实践-DBNet、CRNN、TrOCR

前言 OCR技术作为文档智能解析链路中的核心组件之一&#xff0c;贯穿整个技术链路&#xff0c;包括&#xff1a;文字识别、表格文字识别、公式识别&#xff0c;参看下面这张架构图&#xff1a; 前期介绍了很多关于文档智能解析相关核心技术及思路&#xff0c;本着连载的目的&a…

IT监控平台可视化:多维度展示助力运维效率提升

在信息化时代&#xff0c;IT设备的稳定性与业务的连续性紧密相连&#xff0c;任何细微的故障都可能给企业带来巨大的损失。因此&#xff0c;IT运维团队面临着前所未有的挑战&#xff0c;他们需要迅速、准确地识别和解决问题&#xff0c;以确保业务的平稳运行。而IT监控平台的可…

应届生毕业找不到工作转行IT需要做好哪些准备呢?

前言 相信这是很多即将毕业的应届生们都非常关心的问题。在这里&#xff0c;我们将站在一个应届生毕业且对IT行业感兴趣的角度&#xff0c;来探讨一下这个问题。 首先&#xff0c;我们先来了解一下什么是应届生。应届生是指在学校毕业之后&#xff0c;能够在当年或者下一年度…

AIGC验证码如何对抗,AIGC VS AIGC

AI类型的验证码&#xff0c;当然使用AI对对抗&#xff0c;使用大量的样本叠加训练&#xff0c;我的生成如下: 如果可以生词大量词汇&#xff0c;那么准确率必然上升&#xff0c;有办法的可以讨论

大模型系列:RAG技术深度解析

文末有福利&#xff01; RAG 是2023年最流行的基于 LLM 的应用系统架构。有许多产品几乎完全建立在 RAG 之上&#xff0c;覆盖了结合网络搜索引擎和 LLM 的问答服务&#xff0c;到成千上万个数据聊天的应用程序。很多人将RAG和Agent 作为大模型应用的两种主流架构&#xff0c;…

CTFHUB技能树之HTTP协议——响应包源代码

开启靶场&#xff0c;打开链接&#xff1a; 是个贪吃蛇小游戏&#xff0c;看不出来有什么特别的地方 用burp抓包看看情况&#xff1a; 嗯&#xff1f;点击“开始”没有抓取到报文&#xff0c;先看看网页源代码是什么情况 居然直接给出flag了&#xff0c;不知道这题的意义何在 …

pip离线下载和安装第三方库

pip离线下载和安装第三方库 离线下载离线安装 离线下载 下载依赖库&#xff08;.whl文件&#xff09;&#xff0c;比如下载polars库&#xff0c;并指定重清华源下载&#xff0c;会自动下载依赖的库&#xff0c;并保存到当前目录中&#xff0c;下载命令如下&#xff1a; # 下载…

Intel 新独显 Arc Battlemage 或于10月29日揭晓

原文转载修改自&#xff08;更多互联网新闻/搞机小知识&#xff09;&#xff1a; Arc Battlemage或于10月29日揭晓 英特尔入局独显市场也好几年了&#xff0c;虽然开局声势和跑分都挺猛&#xff0c;但市场表现不会说谎&#xff0c;上季度趋近于0的市场份额就是答案……不过&am…

字符串及正则表达式

目录 字符串 字符串常用方法&#xff1a; 格格式化字符串的三种方式&#xff1a; 格式化字符串的详细格式&#xff1a; 字符串的编码&#xff1a; 字符串的解码&#xff1a; 数据的验证&#xff1a; 字符串拼接的几种方式&#xff1a; 字符串去重&#xff1a; 正则表达…

notepad++中实现代码整体缩进和退格

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业 ✈️个人公众号&#xff1a;乡下小哥编程。回复 Java全套视频教程 或 前端全套视频教…

【网易云音乐】--源代码分享

最近写了一个网易云音乐的音乐实现部分&#xff0c;是通过JavaScript和jQuery实现的&#xff0c;具体效果大家可以参照下面的视频 源代码分享 - git地址: 网易云音乐源代码 下面将着重讲解一下音乐实现部分 视频有点模糊&#xff0c;不好意思&#xff0c;在b站上添加视频的时候…

CSS进阶-布局(一)

1、文本溢出 <style>.d1 {width: 400px;height: 300px;background-color: antiquewhite;/* 超出部分色设置为可见&#xff0c;默认方式 *//* overflow: visible; *//* 超出部分使用滚动条 *//* overflow: scroll; *//* 如果内容未超出元素则正常显示&#xff0c;超出元素…

免费企业邮箱哪个好:全面对比与推荐烽火!

免费企业邮箱哪个好用&#xff1f;推荐的免费企业邮箱有哪些&#xff1f; 面对市场上众多的免费企业邮箱服务&#xff0c;企业该如何选择&#xff1f;烽火将深入探讨免费企业邮箱哪个好这一问题&#xff0c;通过全面对比与推荐&#xff0c;帮助企业找到最适合自己的邮箱服务。…

OpenCV高级图形用户界面(15)注册一个回调函数来处理鼠标事件的函数setMouseCallback()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 为指定的窗口设置鼠标处理器。 setMouseCallback 是 OpenCV 中的一个功能&#xff0c;允许开发者注册一个回调函数来处理鼠标事件。当用户在窗口…

【前端】如何制作一个自己的网页(6)

接上文 网络中的图片 我们也可以在百度等网站搜索自己喜欢的图片。 此时对图片点击右键&#xff0c;选择【复制图片地址】&#xff0c;即可获得该图片的网络地址。 其实在HTML中&#xff0c;除了图片以外&#xff0c;我们还可以利用地址找到另一个网页。 如右图所示&#…

【Linux】理解文件系统与软硬链接,观察inode号理解<“软链接是包含路径的新文件“,“硬链接是关于文件名的机制“>,最终明白<什么是真正删除一个文件>

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

ComfyUI一键更换服装:IP-Adapter V2 + FaceDetailer(DeepFashion)

在这篇文章中&#xff0c;我们将探索如何使用新版的IP-Adapter和ComfyUI软件为人物进行换装。 整个过程非常简单&#xff0c;仅需要两张图片&#xff1a;一张服装图片和一张人物图片。 通过一系列节点的操作&#xff0c;ComfyUI就会把这个服装换到人物身上&#xff0c;并利用…

【DS】哈希表,哈希桶的实现

目录 哈希概念哈希冲突哈希函数负载因子哈希冲突的解决闭散列开散列 哈希表闭散列的实现哈希表的结构哈希函数构造函数查找插入删除 哈希表开散列的实现哈希表的结构查找插入删除 哈希表的表长建议是素数 平衡二叉树的学习中&#xff0c;学习及模拟实现了AVL树和红黑树&#xf…