目录

一、实现框架

二、list_node节点类的模拟实现

节点构造函数

三、list_iterator迭代器的模拟实现

迭代器类的模板参数说明

构造函数

*运算符重载

++运算符的重载

--运算符的重载

==运算符的重载

!=运算符的重载

list的模拟实现

默认成员函数

构造函数

拷贝构造函数

赋值运算符重载函数

析构函数

迭代器相关函数

begin和end

插入、删除函数

push_back

insert

erase

push_front

pop_back

pop_front

size

swap

clear


一、实现框架

namespace lzg
{//模拟实现list当中的结点类template<class T>struct list_node{//成员变量T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T x& = T()):_data(x),_next(nullptr),_prev(nullptr){}};//模拟实现list迭代器//这里写的一种是模板,最后由编译器实例化对应的类型template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}//各种运算符重载Ref operator*();Ptr operator->();Self& operator++();Self& operator--();Self operator++(int);Self operator--(int);bool operator!=(const Self& s);bool operator==(const Self& s);};template<class T>class list{typedef list_node<T> Node;public://迭代器有两种(const和非const),通过传递的参数(有无const)来实例化对应迭代器typedef  list_iterator<T, T&, T*> iterator;typedef  list_iterator<T, const T&, const T*> const_iterator;//初始化空的带哨兵位的双向循环链表void list_empty();list();//默认构造函数(调用list_empty();)list(const list<T>& lt);//拷贝构造函数list(size_t n, const T& val = T());//初始化n个值为val的链表list<T>& operator=(list<T> lt); //赋值重载~list();  //析构//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//链表的操作函数void push_front(const T& x);void pop_front();void pop_back();void push_back(const T& x);iterator insert(iterator pos, const T& val);iterator erase(iterator pos);void clear();void swap(list<T>& tmp);size_t size();private:Node* _head;size_t _size;};}

二、list_node节点类的模拟实现

list的底层是带头双向循环链表

因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)

而对于该结点类的成员函数来说,我们只需实现一个构造函数即可。因为该结点类只需要根据数据来构造一个结点即可,而结点的释放则由list的析构函数来完成。

节点构造函数

list_node(const T x& = T()):_data(x),_next(nullptr),_prev(nullptr)
{}

传值时利用匿名构造来实现,这样节点就能存储任意类型的值

三、list_iterator迭代器的模拟实现

在之前string和vector中模拟实现迭代器我们都是在类里面完成的,并没有单独拎出来封装成一个类,这是因为在前两者中,物理地址空间是连续的可以完成自增自减,但链表不一样,两个节点的地址不是连续的不能通过+1来实现从一个节点到另一个节点的操作。

你可能还会问,那么为什么不在list里面封装形成一个内部类,这也是不合理的,这样会

降低灵活性​:难以共享迭代器实现细节(如const和非const迭代器)

增加耦合​:迭代器实现与特定容器绑定

模板特化困难​:不利于针对不同迭代器类别进行优化

迭代器类的模板参数说明

这里我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?

template<class T, class Ref, class Ptr>

我们是仿照c++底层源码实现的,Ref是reference(引用)的缩写,Ptr是pointer(指针)的缩写

我们在list中typedef了两种迭代器

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

传递的形参没有加const(<T, T&, T*> )也就是对应普通迭代器( iterator;)

加上了const修饰那么就是const迭代器 (const_iterator)

可以理解我们写的是一个迭代器模板,通过传递的参数不同由编译器帮我们实例化出是普通迭代器还是const迭代器,如果不用Ref和Ptr我们就要写两种迭代器并且它们是高度相似的

构造函数

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

list_iterator(Node* node):_node(node)
{}

*运算符重载

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

Ref operator*()
{return _node->_data;//访问节点中存储的数据
}

->运算符重载

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

++运算符的重载

typedef list_iterator<T, Ref, Ptr> Self;

前置++

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& s)
{return _node == s._node;
}

!=运算符的重载

可以复用==,也可以看两个迭代器的指针指向是否不同

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

四、list的模拟实现

默认成员函数

构造函数

list是一个带头双向循环链表,在构造一个list对象时,直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可。

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

拷贝构造函数

拷贝构造函数就是根据所给list容器,拷贝构造出一个新对象。对于拷贝构造函数,我们先调用list_empty函数把链表初始化为空,再通过复用push_back函数一个个尾插到新构造的容器后面即可。

//lt2(lt1)
list(const list<T>& lt)
{list_empty();for (auto& e : lt){push_back(e);//将容器lt当中的数据一个个尾插到新构造的容器后面}
}

赋值运算符重载函数

对自定义类型传值传参要调用对应的拷贝构造函数,我们通常加上&(引用)是为了减少传值传参带来的拷贝冗余,但是我们这里故意不用引用,那么lt就是lt1的拷贝,通过交换lt来实现赋值,这样把lt1赋值给了lt2,并且我们交换的是lt(lt1的拷贝)并不会影响lt的数据,并且出函数时,临时对象lt会自动析构

// lt2=lt1                 
list<T>& operator=(list<T> lt)
{            swap(lt);   return *this;
}

析构函数

//析构函数
~list()
{clear(); //清理容器delete _head; //释放头结点_head = nullptr; //头指针置空
}

clear的实现在后面,并且clear也是复用erase函数

迭代器相关函数

begin和end

begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。

由于返回类型是iterator (list_iterator<T,T&,T*>) 如果写出return _head->_next,编译器不会自动将指针转为自定义类类型,这里我们用迭代器的匿名构造返回地址

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

当然,还需要重载一对用于const对象的begin函数和end函数

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

插入、删除函数

push_back

画图后操作一目了然,完成insert函数后就能复用

void push_back(const T& x)
{/*Node* new_node = new Node(x);Node* tail = _head->_prev;tail->_next = new_node;new_node->_prev = tail;new_node->_next = _head;_head->_prev = new_node;*/insert(end(), x);
}

insert

iterator insert(iterator pos, const T& val)
{Node* cur = pos._node;       //pos是一个类需要的是里面的_node值Node* newnode = new Node(val);Node* prev = cur->_prev;//prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;_size++;return iterator(newnode);
}

erase

iterator erase(iterator pos)
{assert(pos != end());Node* del = pos._node;Node* prev = del->_prev;Node* next = del->_next;//prev del nextprev->_next = next;next->_prev = prev;delete del;_size--;return iterator(next);
}

push_front

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

pop_back

void pop_back()
{erase(--end());
}

pop_front

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

size

为了方便在list中我们还定义了_size来表明链表中插入节点的个数,并且在上面insert和erase函数中都更新了对_size的操作,所以我们直接返回就行了

size_t size()
{return _size;
}

swap

交换两个链表的哨兵位节点就能交换两个链表

void swap(list<T>& tmp)
{swap(_head,tmp._head);swap(_size, tmp._size);
}

clear

clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点即可。

void clear()
{auto it = begin();while (it != end()){it = erase(it);}
}

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

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

相关文章

解决网站图片加载慢:从架构原理到实践

在当前的数字商业环境中&#xff0c;用户的在线体验至关重要。当一个潜在客户访问企业网站或电商平台时&#xff0c;如果页面加载过程迟缓&#xff0c;特别是图片和视频内容无法快速显示&#xff0c;用户的耐心会迅速耗尽。研究数据表明&#xff0c;网站加载时间与用户跳出率和…

windows注册表:开机自启动程序配置

目录 一、注册表位置 系统范围的开机自启动程序 当前用户的开机自启动程序 二、配置步骤 三、注意事项 四、其他方法 任务计划程序 启动文件夹 1. 创建程序快捷方式 2. 打开 Startup 文件夹 3. 将快捷方式移动到 Startup 文件夹 4. 验证程序是否自动启动 注意事项 …

(11)用于无GPS导航的制图师SLAM(一)

文章目录 前言 1 安装 RPLidar 和 Pixhawk 2 检查 RPLidar 的串行端口 3 安装更多软件包 4 创建Catkin工作空间 5 安装 RPLidar 节点 6 安装 Google Cartographer 前言 本页展示了如何使用 RPLidarA2 激光雷达(RPLidarA2 lidar)设置 ROS 和 Google Cartographer SLAM&a…

车载诊断架构 --- 基于整车功能的正向诊断需求开发

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

字帖生成器怎么用?电脑手机双端操作指南

字帖生成器是一款支持电脑端和手机端的免费练字工具&#xff0c;可一键生成PDF格式字帖并直接打印使用。本文基于官方公开版本&#xff0c;提供无广告、无营销的实测操作指南。 工具基础信息 软件名称&#xff1a;字帖生成器适用设备&#xff1a;Windows、安卓/鸿蒙核心功能&…

pycharm 远程连接服务器报错

配置远程链接的时候出现报错 Command finished with exit code 139 Execution was killed due to timeout Failed to execute command Rsync command ‘rsync’ was not found neither in local PATH nor as full executable path Starting introspection for Python… 放假前好…

局域网共享文件夹

准备工作&#xff1a; A电脑&#xff08;共享端&#xff09; B电脑&#xff08;本机&#xff09;在A电脑&#xff0c;选好要共享的目录&#xff0c;然后右键属性 > 高级共享 > 共享此文件夹 > 权限(全开)然后找到此电脑&#xff0c;右键&#xff0c;打开属性&#xff…

时序数据库全景指南:从场景选型到内核拆解

1. 什么是时序数据 时序数据&#xff08;Time-Series Data&#xff09; 是在时间上连续产生、且带有时间戳的观测值序列&#xff0c;典型特征&#xff1a;维度描述高并发写百万点/秒&#xff0c;追加为主写多读少90 % 查询是降采样或聚合时效性越新越热&#xff0c;旧数据价值递…

深入解析 Oracle 内存架构:驾驭 SGA 与 PGA 的性能艺术

引言&#xff1a;数据库的心脏与大脑如果说磁盘上的数据文件是 Oracle 数据库的“身体”&#xff0c;是永久存储的基石&#xff0c;那么内存结构就是其“心脏与大脑”。它负责所有计算活动的发生&#xff0c;决定了数据泵送的速度与效率。一个配置得当、运行顺畅的内存体系&…

竣工验收备案识别技术:通过AI和OCR实现智能化文档处理,提升效率与准确性,推动建筑行业数字化转型。

竣工验收备案是建设工程项目投入使用的最终法定程序&#xff0c;是确保工程符合规划、质量、消防、环保等各项要求的核心关口。传统的备案流程依赖大量纸质文档和人工审核&#xff0c;效率低下且易出错。随着人工智能与大数据技术的崛起&#xff0c;竣工验收备案识别技术应运而…

76 最小覆盖子串

76 最小覆盖子串 文章目录76 最小覆盖子串1 题目2 解答1 题目 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 "" 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;…

趣味学Rust基础篇(变量与可变性)

这篇文章将用通俗的比喻和清晰的逻辑&#xff0c;带你深入理解 Rust 变量背后的核心思想&#xff0c;让你不仅“会用”&#xff0c;更能“明白为什么”。 Rust 的“盒子哲学”&#xff1a;变量、可变性、常量与隐藏 想象一下&#xff0c;Rust 里的变量就像一个个盒子。你把值&a…

2025年- H100-Lc208--912.排序数组(快速选择排序)--Java版

1.题目2.思路 快速选择排序的平均时间复杂度是O&#xff08;nlogn&#xff09;&#xff0c;最坏时间复杂度是O&#xff08;n^2&#xff09;&#xff0c;最好的时间复杂度是O&#xff08;nlogn&#xff09;&#xff0c;空间复杂度是O&#xff08;nlogn&#xff09;。 排序算法中…

解决 pdf.mjs 因 MIME 类型错误导致的模块加载失败问题

Mozilla PDF.js V4 开始&#xff0c;它官方分发确实只提供了 ESM 模块&#xff08;.mjs&#xff09;&#xff0c;没有以前的 pdf.js、pdf.worker.js UMD 版本了。 这个问题本质上是 浏览器要求以 application/javascript MIME 类型加载 ES Module&#xff0c;而你引入的 pdf.mj…

STM32八大模式

前言&#xff1a;STM32存在八大模式&#xff0c;分别如下推挽输出&#xff0c;开漏输出&#xff0c;复用推挽输出&#xff0c;复用开漏输出浮空输入&#xff0c;上拉输入&#xff0c;下拉输入&#xff0c;模拟输入STM32标准IO结构图如下&#xff1a;其中如下电路为保护电路&…

OpenCV4.X库功能全解---个人笔记

文章目录前言1.Core核心功能1.1 基本数据类型和结构&#xff1a;1.2 数组操作&#xff1a;1.3 数学函数&#xff1a;1.4 随机数生成&#xff1a;1.5 线性代数运算&#xff1a;1.6 常用数据结构和算法&#xff1a;1.7 XML/YAML文件读写&#xff1a;1.8 错误处理&#xff1a;1.9时…

代码随想录刷题Day44

二叉搜索树的最近公共祖先 这道题&#xff0c;可以沿用二叉树的最近公共祖先的求法进行求解&#xff0c;也就是root判断-左右子树递归求LCA-根据左右子树的LCA结果返回值这一套。 但是&#xff0c;如果要用上搜索二叉树的有序性这个信息的话&#xff0c;就可以直接在递归时候确…

springmvc的数据校验和处理的一个例子

JSR-303是Java 的标准规范&#xff0c;而 Spring MVC 对其提供了完美的支持和集成 1.JSR-303 的身份 JSR-303 是 Java 标准 JSR&#xff1a;Java Specification Request&#xff08;Java 规范请求&#xff09; JSR-303&#xff1a;Bean Validation 1.0&#xff08;Bean 验证规范…

SlowFast使用指南(三)——自建数据集

写在前面 在前两个章节初步使用了SlowFast&#xff0c;使用的都是官方给出的数据集。 附上链接&#xff1a; SlowFast使用指南&#xff08;一&#xff09;——demo运行-CSDN博客 SlowFast使用指南&#xff08;二&#xff09;——训练ava数据集-CSDN博客 本文尝试了使用自己的数…

Day26 树的层序遍历 哈希表 排序算法 内核链表

day26 树的层序遍历 哈希表 排序算法 内核链表 实现树的层序遍历&#xff08;广度遍历&#xff09; 使用队列辅助实现二叉树的层序遍历。算法核心思想是&#xff1a;从根节点开始&#xff0c;依次将每一层的节点入队&#xff0c;出队时访问该节点&#xff0c;并将其左右子节点&…