【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比

news2024/11/15 17:19:23

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++
  🔝🔝


在这里插入图片描述


list模拟实现

  • 1. 前言
  • 2. list类的大致框架与结构
  • 3. List类的构造,析构,拷贝构造
  • 4. list的迭代器的实现
    • 4.1 list迭代器的若干函数解析
    • 4.2 list迭代器的解引用和箭头操作
    • 4.3 迭代器类映射到list类
  • 5. const迭代器实现深度剖析
    • 5.1 const迭代器实现详解
    • 5.2 const迭代器和list类的复用
    • 5.3 const迭代器使用实例
  • 6. list和vector的对比
  • 7. 总结以及代码分享

1. 前言

本篇文章立足于上一篇文章:
list深度剖析(上)
请先阅读完上一篇文章后再阅读这篇文章!

本章重点:

本章着重讲解list的模拟实现
list模拟实现的重难点是迭代器的实现
和const迭代器的实现
最后总结list和vector的区间对比

注:我将在文章末尾分享list模式实现全部代码


2. list类的大致框架与结构

由于list类是一个带头双向循环链表
所以在写list类之前,应该先定义节点类
在真正的list类中会复用此类:

template<class T>
class list_node
{
public:
	T _data;
	list_node<T>* _next;
	list_node<T>* _prev;
	list_node(const T& x = T())
		:_data(x)
		, _next(nullptr)
		, _prev(nullptr)
	{}
};

这个类和用C语言实现链表的定义很像
节点中存储一个T模板类型的值和
上一个节点的地址和下一个节点的地址

在List类中,由于链表都是些链接关系
所以List类中的成员变量只需要定义一个
那就是头节点head,知道head的链接关系
就知道list类对象中存放的内容!

List类基本框架以及无参构造:

template<class T>
class List
{
	typedef list_node<T> node;
public:
	List()
	{
		_head = new node;
		_head->_next=_head;
		_head->_prev=_head;
	}
private:
	node* _head;
}

给头节点head开辟一份空间后
头节点的指向最开始都是指向自身:

在这里插入图片描述


3. List类的构造,析构,拷贝构造

无参构造函数以及写过了,我们模仿
STL库中的带参构造写一个用迭代器
区间初始化的构造函数:

void emptyinit()//创建并初始化哨兵位的头节点,方便给构造和拷贝构造复用
	{
		_head = new node;
		_head->_next = _head;
		_head->_prev = _head;
	}
template<class InputTterator>//有参的构造
List(InputTterator first, InputTterator last)
{
	emptyinit();
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

注:push_back先用着,后面会实现

在实现析构函数前,我们可以先实现clear函数
因为析构函数实际上可以复用clear函数:

void clear()//清空数据,除了头节点
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);//erase会返回下一个节点的迭代器
	}
}

~List()//_head也要被删除掉
{
	clear();
	delete _head;
	_head = nullptr;
}

注:迭代器相关的函数先用着,后面会实现

拷贝构造函数的实现:(用lt1拷贝lt2)

//lt2(lt1)
List(const List<T>& lt)//完成深拷贝
{
	emptyinit();
	List<T> tmp(lt.begin(), lt.end());//用lt1的迭代器区间去构造一下tmp
	::swap(_head, tmp._head);
}

此拷贝构造函数精妙的地方有两个:

  1. 它先定义一个临时变量tmp来接受
    lt1的所有值,然后再将临时变量tmp
    的head和lt2的head交换,这样一来
    lt2中的链接关系就和lt1相同了
    并且tmp变量是构造函数初始化的
    它是深拷贝,所以lt2对于lt1也是深拷贝
  1. tmp是临时变量,除了作用域会销毁
    也就是除了此拷贝构造函数后会销毁
    销毁时会调用析构函数,然而lt2的head
    以及和tmp的head交换了,所以tmp销毁
    时实际上是在帮原先的lt2销毁内存!

4. list的迭代器的实现

由于list的迭代器底层不是简单的指针
所以我们不能像实现vector一样直接在
list类定义迭代器和使用迭代器相关函数

要重新写一个迭代器类来实现功能:

template<class T>
struct __list_iterator
{
	typedef list_node<T> node;
	typedef __list_iterator iterator;
	node* _node;
}

这样重新写一个类的作用是将迭代器的
++,- -等操作在此类中实现,因为list中的
++实际上是node=node->next,然而list
中的==符号判断的是node1->data和
node2->data相不相同,如果迭代器和
list写在一个类中,实现此等操作会很麻烦


4.1 list迭代器的若干函数解析

1. 构造函数:

__list_iterator(node* nnode)
	:_node(nnode)
	{}

由于初始化列表会调用node的构造函数
所以list迭代器的构造函数可写可不写

2. 前置++/- -和后置++/- -

iterator& operator++()//前置++
{
	_node = _node->_next;
	return *this;
}
iterator& operator++(int)//后置++
{
	iterator tmp = *this;
	_node = _node->_next;
	return tmp;
}
iterator& operator--()//前置--
{
	_node = _node->_prev;
	return *this;
}
iterator& operator--(int)//后置--
{
	iterator tmp = *this;
	_node = _node->_prev;
	return tmp;
}

3. 等于和不等于函数解析:

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

4.2 list迭代器的解引用和箭头操作

迭代器的使用就像指针一样,所以
解引用后应该直接得到节点的数据!

由于结构体变量除了可以用解引用
以外还能使用->,所以需要写两个函数:

//可读可写
T& operator*()
{
	return _node->_data;
}
//可读可写
T* operator->()
{
	return &(operator*());
}

解引用操作的应该没有问题
重点难点在这个箭头->函数
可以将这个函数一步一步拆分:

return &(_node->_data);

相当于返回了一个结构体指针

然而我们想要的并不是一个结构体指针
而是确切的值,蛋这样写编译器并不会报错

这是因为编译器为了代码的可读性
帮我们省略了一个箭头->
所以只需要一个箭头->就能访问数据!

注:省略的箭头可以将返回的结构体指针解引用
然而此结构体指针解引用后其实就是data


4.3 迭代器类映射到list类

当我们实现完迭代器类后
就可以在list类中复用迭代器类了:

template<class T>
class List
{
	typedef list_node<T> node;
	typedef __list_iterator<T> iterator;
private:
	node* _head;

有了迭代器类的加持后,list类中的
其他函数如begin和end就是歪瓜裂枣了

iterator begin()
{
	//iterator tmp(_head->_next);
	//return tmp;
	return iterator(_head->_next);//使用了匿名对象
}
iterator end()
{
	//iterator tmp(_head);
	//return tmp;
	return iterator(_head);
}

注:其他关于迭代器的函数会在最后放出

5. const迭代器实现深度剖析

既然list中的迭代器和vector中的不同
那么const迭代器的实现当然也不同

首先我们要明白一点,list的普通迭代器
和const迭代器的区别到底是什么?

const对象的剖析:

const迭代器是为const对象准备的
然而const对象的特征就是不能修改
那么什么操作会让对象的值变化呢?
答案很明显是解引用操作和箭头->
begin和end函数返回后也有可能被修改
注:返回值是T&和T*的函数都要注意

解决方案剖析:

大家可能第一时间想到再重新写一个
const迭代器的类,里面的实现和普通
迭代器都一样,唯一不同的是解引用函数
和箭头->函数的返回值是const对象

在这里插入图片描述


5.1 const迭代器实现详解

首先,不用再重新写一个类来实现
接下来的方案不要问为什么
不要问怎么想到的,是天才想到的:

在普通迭代器类中使用三个模板参数

为什么要这样做?

通过观察可以发现,只有两个函数不同
并且这两个函数的类型只有T&和T*
那么就专门为这两个类型设置两个模板
只有在编写这两个函数时用到这两个模板
其余函数还是正常的使用T来当类型

话不多说,上代码

template<class T, class Ref, class Ptr>
struct __list_iterator//迭代器不需要析函数
{
	typedef list_node<T> node;
	typedef __list_iterator iterator;
	node* _node;
}

模板中的Ref代表的是引用&
模板中的Ptr代表的是指针
*

现在重新来实现一下这两个函数:

//按模板参数来
Ref operator*()
{
	return _node->_data;
}
Ptr operator->()
{
	return &(operator*());
}

现在你脑子肯定是一篇空白
但是精髓的地方马上就来了,请耐住性子


5.2 const迭代器和list类的复用

当list类复用了此迭代器类后:

template<class T>
class List
{
	typedef list_node<T> node;
	typedef __list_iterator<T, T&, T*> iterator;
	typedef __list_iterator<T, const T&, const T*> const_iterator;
private:
	node* _head;
}

这样写出来后,普通迭代器和const迭代器
就被完美的区别开了,当传入const对象时
系统会把模板参数实例化为const T&
当传入的是普通对象时,系统会把模板参数
实例化为普通的T,T&和T*

begin和end函数的复写:

const_iterator begin()const
{
	return const_iterator(_head->_next);
}
iterator begin()
{
	return iterator(_head->_next);//使用了匿名对象
}
const_iterator end()const
{
	return const_iterator(_head);
}
iterator end()
{
	return iterator(_head);
}

5.3 const迭代器使用实例

现在,我们通过一个实例来感受这一过程:

void print_list(const list<int>& lt)
{
	auto it = lt.begin();
	while (it != lt.end())
	{
		//*it = 10;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

此时的lt变量是const变量,实例化类时
会实例化为<T,const T&,const T*>
然后回退到迭代器类时,迭代器类的
模板参数Ref就对应:const T&
模板参数Ptr就对应:const T*

此时解引用函数的返回值就是const T&
如果你写:*it=10;那么就会报错!


6. list和vector的对比

详情请看下表:

目录vectorlist
底层结构顺序表,空间连续带头双向循环链表
随机访问支持随机访问,访问效率为O(1)不支持随机访问,访问效率为O(N)
插入和删除任一位置插入删除效率低,还需扩容任一位置插入效率高
空间利用率空间,缓存利用率高不连续空间,容易造成内存碎片
迭代器原生态的指针对原生指针的封装
迭代器失效插入和删除都会导致只有删除会导致
使用场景高效存储,支持随机访问有大量插入和删除操作,不关心随机访问

注:这个表格不能死记硬背,要理解记忆


7. 总结以及代码分享

总的来说,list的底层实现较于vector
来说要复杂一点,这其中的底层原因
就是list的迭代器还需要一层封装,而
vector的迭代器不需要额外封装

C++的强大就在于把复杂的底层
全部封装起来了,而表面的使用上
list和vector并无太大区别,这就是
C++封装的魅力!

list模拟实现全部代码分享:

List模拟实现全部代码

注:全部代码中包含反向迭代器
目前阶段可以跳过不管


🔎 下期预告:栈和队列 🔍

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

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

相关文章

开源协议对比:局限性、应注意事项与详细对比

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

浙江工业大学MBA和浙江工商大学MBA哪个容易上岸?

在浙江省内&#xff0c;一般嫌弃浙大MBA项目学费贵的考生基本会从其它八个MBA项目中做衡量选择&#xff0c;其中浙工大MBA和浙工商MBA项目就是不少考生经常会做对比的项目&#xff0c;究竟哪个项目更容易上岸也是大家所关注的话题之一&#xff0c;立足浙江的杭州达立易考教育结…

从融云数智办公平台,看企业需要什么样的大模型?

本文中&#xff0c;我们将聚焦 ToB 领域&#xff0c;打开 AIGC 在 C 端社交泛娱乐之外的另一个切面&#xff0c;探索 B 端叙事的新变化。关注【融云 RongCloud】&#xff0c;了解协同办公平台更多干货。 过去两年&#xff0c;关于互联网大厂最多的消息当属裁员和关停非短期商业…

2.9 PE结构:重建导入表结构

脱壳修复是指在进行加壳保护后的二进制程序脱壳操作后&#xff0c;由于加壳操作的不同&#xff0c;有些程序的导入表可能会受到影响&#xff0c;导致脱壳后程序无法正常运行。因此&#xff0c;需要进行修复操作&#xff0c;将脱壳前的导入表覆盖到脱壳后的程序中&#xff0c;以…

速看!外滩大会发布银行数字科技5大趋势

通用人工智能风起云涌&#xff0c;金融行业将如何应对&#xff1f; 9月8日&#xff0c;由中国银行业协会指导&#xff0c;网商银行承办的外滩大会银行业数字化论坛上&#xff0c;IDC中国副总裁兼首席分析师武连峰发布了《银行数字科技五大趋势》&#xff1a;随身银行、AI风控、…

群晖(Synology)NAS 后台安装 Docker 后配置 PostgreSQL

群晖&#xff08;Synology&#xff09;NAS 的后台在新版本对 Docker 不再称为 Docker&#xff0c;现在改称为 Container Manager 了。 单击进入后运行 Container Manager。 PostgreSQL 容器 针对 PostgreSQL 的容器&#xff0c;我们选择容器后&#xff0c;如果你已经安装了 P…

buffer pool原理总结

innodb buffer pool原理总结 文章目录 innodb buffer pool原理总结1. 缓存的重要性2. innodb buffer pool2.1 buffer pool的内部组成2.2 FREE链表2.3 FLUSH链表2.4 LRU链表2.4.1 LRU链表的功能预读 1. 缓存的重要性 我们都知道&#xff0c;对于innodb存储引擎的表来说&#xf…

快手用户活跃度分析(未完成)

目标 为期30天的用户数据&#xff0c;但是不是所有的用户都有30天的信息数据&#xff0c;比如用户A第7天注册的&#xff0c;则其前6天没有数据。 预测未来用户活跃度的可能性。 预测7天后的&#xff0c;基于第7天&#xff0c;预测第14天&#xff0c;基于第8天&#xff0c;预测…

2023数模A题——定日镜场的优化问题

A题——定日镜场的优化问题 思路&#xff1a;该题主要考察的几何知识和天文学知识&#xff0c;需要不同角度下的镜面和遮挡情况。 资料获取 问题1&#xff1a; 若将吸收塔建于该圆形定日镜场中心&#xff0c;定日镜尺寸均为 6 m6 m&#xff0c;安装高度均为 4 m&#xff0c;且…

解密Kubernetes(K8s)集群的创建过程和关键步骤

文章目录 1. 准备环境2. 安装Docker3. 安装Kubernetes在Master节点上执行以下步骤&#xff1a;安装kubeadm、kubelet和kubectl初始化Master节点 在工作节点上执行以下步骤&#xff1a;加入集群 4. 设置Kubeconfig5. 安装网络插件6. 验证集群7. 部署应用程序8. 扩展和管理集群9.…

Weblogic反序列化漏洞

文章目录 1、搭建环境2、漏洞特征3、漏洞利用1)获取用户名密码2)后台上传shell 4、检测工具 1、搭建环境 漏洞环境基于vulhub搭建–进入weak_password的docker环境 sudo docker-compose up -d拉取靶场 2、漏洞特征 404特征Weblogic常用端口&#xff1a;7001 3、漏洞利用…

centos7使用docker-compose一键搭建mysql高可用主从集群

docker部署 环境准备 卸载旧版本 yum remove -y docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \docker-engine 安装依赖 yum install -y yum-utils \…

PCB - 封装焊盘阻焊层的检查

文章目录 PCB - 封装焊盘阻焊层的检查概述检查做出的实际PCB正反面厂家提供的生产稿PCB对应的原始gerber文件查封装拿一个插件电阻为例插件封装焊盘的基本数据END PCB - 封装焊盘阻焊层的检查 概述 打样回来, 看到要焊接的几个插件管脚有阻焊, 无法焊接. 这几个封装是直接从第…

腾讯发布超千亿参数规模的混元大模型;深度学习与音乐分析与生成课程介绍

&#x1f989; AI新闻 &#x1f680; 腾讯发布超千亿参数规模的混元大模型 摘要&#xff1a;腾讯在2023腾讯全球数字生态大会上发布混元大模型&#xff0c;该模型拥有超千亿的参数规模和超2万亿 tokens 的预训练语料。混元大模型将支持多轮对话、内容创作、逻辑推理、知识增强…

CleanShot X for mac安装下载,mac系统录屏、截图、标注软件

您是否经常需要截图、录屏或者标注图片&#xff1f;如果是&#xff0c;那么您一定会喜欢CleanShot X for mac&#xff0c;这是一款专为Mac用户设计的强大而简洁的工具。 CleanShot X for mac可以让您轻松地截取任何区域的屏幕&#xff0c;无论是整个屏幕、窗口还是选定的部分。…

Say Goodbye to OOM Crashes

内存管理 --- 在计算机编程中&#xff0c;内存管理是一项关键任务&#xff0c;用于在程序运行时正确分配和释放内存。一个有效的内存管理系统可以帮助程序提高性能&#xff0c;减少内存泄露和访问错误等问题。 内存管理涉及以下几个方面&#xff1a; 1. 内存分配&#xff1a…

MMDetection实验记录踩坑记录

AP值始终为0 在实验MMDetection的DAB-DETR模型进行实验时&#xff0c;AP值始终上不去。 可以看到&#xff0c;在第22个epoch时的AP值仅为0.002 因为在此之前已经运行过YOLOX,Faster-RCNN等模型&#xff0c;所以数据集的设置肯定是没有问题的&#xff0c;而博主也只是修改了DAB…

嵌入式学习笔记(18)代码重定位实战 下篇

adr和ldr伪指令的区别 ldr和adr都是伪指令&#xff0c;区别是ldr是长加载、adr是短加载。 adr指令加载的是运行时地址&#xff1b;ldr指令加载的是链接地址。 &#xff08;通过反汇编文件可以深入分析adr和ldr的区别&#xff09; 重定位&#xff08;代码拷贝&#xff09; …

行业Demo分享|「园区智慧安防可视化系统」实现园区安防全面保障

在当今社会&#xff0c;园区安全问题备受关注。**为了解决园区的安全隐患并提升安全管理水平&#xff0c;园区智慧安防可视化系统应运而生。**这一系统利用先进的技术手段&#xff0c;将智能监控与安全管理完美地融合在一起&#xff0c;并通过可视化的方式&#xff0c;为园区提…

无swing,高级javaSE毕业之贪吃蛇游戏(含模块构建,多线程监听服务)

JavaSE&#xff0c;无框架实现贪吃蛇 文章目录 JavaSE&#xff0c;无框架实现贪吃蛇1.整体思考2.可能的难点思考2.1 如何表示游戏界面2.2 如何渲染游戏界面2.3 如何让游戏动起来2.4 蛇如何移动 3.流程图制作4.模块划分5.模块完善5.0常量优化5.1监听键盘服务i.输入存储ii.键盘监…