【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
💓博主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);
}
此拷贝构造函数精妙的地方有两个:
- 它先定义一个临时变量tmp来接受
lt1的所有值,然后再将临时变量tmp
的head和lt2的head交换,这样一来
lt2中的链接关系就和lt1相同了
并且tmp变量是构造函数初始化的
它是深拷贝,所以lt2对于lt1也是深拷贝
- 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的对比
详情请看下表:
| 目录 | vector | list |
|---|---|---|
| 底层结构 | 顺序表,空间连续 | 带头双向循环链表 |
| 随机访问 | 支持随机访问,访问效率为O(1) | 不支持随机访问,访问效率为O(N) |
| 插入和删除 | 任一位置插入删除效率低,还需扩容 | 任一位置插入效率高 |
| 空间利用率 | 空间,缓存利用率高 | 不连续空间,容易造成内存碎片 |
| 迭代器 | 原生态的指针 | 对原生指针的封装 |
| 迭代器失效 | 插入和删除都会导致 | 只有删除会导致 |
| 使用场景 | 高效存储,支持随机访问 | 有大量插入和删除操作,不关心随机访问 |
注:这个表格不能死记硬背,要理解记忆
7. 总结以及代码分享
总的来说,list的底层实现较于vector
来说要复杂一点,这其中的底层原因
就是list的迭代器还需要一层封装,而
vector的迭代器不需要额外封装
C++的强大就在于把复杂的底层
全部封装起来了,而表面的使用上
list和vector并无太大区别,这就是
C++封装的魅力!
list模拟实现全部代码分享:
List模拟实现全部代码
注:全部代码中包含反向迭代器
目前阶段可以跳过不管
相关文章:
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
💓博主CSDN主页:杭电码农-NEO💓 ⏩专栏分类:C从入门到精通⏪ 🚚代码仓库:NEO的学习日记🚚 🌹关注我🫵带你学习C 🔝🔝 list模拟实现 1. 前言2. list类的大致框架与结构…...
Docker安装RabbitMQ集群_亲测成功
先安装Docker Centos7离线安装Docker 华为云arm架构安装Docker RabbitMQ集群模式介绍 RabbitMQ集群搭建和测试总结_亲测 RabbitMQ 有三种模式:单机模式,普通集群模式,镜像集群模式。单机模式即单独运行一个 rabbitmq 实例,而…...
50道基础数据结构面试题
程序员必备的50道数据结构和算法面试题 在本文中,将分享一些常见的编程面试问题,这些问题来自于不同经验水平的程序员,囊括从刚大学毕业的人到具有一到两年经验的程序员。 编码面试主要包括数据结构和基于算法的问题,以及一些诸…...
【Linux基础】权限管理
👻内容专栏: Linux操作系统基础 🐨本文概括: 用户之间的切换、sudo提权、Linux权限管理、文件访问权限的相关方法、目录权限、粘滞位等 🐼本文作者: 阿四啊 🐸发布时间:2023.9.11 …...
C++初阶--类和对象(中)
目录 类的6个默认成员函数构造函数使用方法 析构函数使用方法 拷贝构造函数使用方法 赋值运算符重载赋值运算符重载 const成员 上篇末尾我们讲到了关于c实现栈相较于c语言在传递参数时的一些优化,但实际上,c在 初始化 清理 赋值 拷贝等方面也做了很大程…...
【MySQL系列】视图特性
「前言」文章内容大致是MySQL事务管理。 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 目录 视图1.1 视图概念1.2 创建视图1.3 修改互相影响1.4 删除视图1.5 视图规则和限制 视图 1.1 视图概念 视图是一个虚拟表,其内容由查询定义同真实的表一样…...
管理类联考——数学——汇总篇——知识点突破——应用题——最值问题
⛲️ 一、考点讲解 最值问题是应用题中最难的题目,也是考生普遍丢分的题目。最值问题一般要结合函数来分析,一般结合二次函数和平均值定理求解。最值问题的求解步骤是:先设未知变量,然后根据题目建立函数表达式,最后利…...
学习SpringMvc第二战之【SpringMVC之综合案例】
目录 一. 参数传递 1.前期准备工作(替换pom.xml中的部分依赖) 1.1将log4j替换成为slf4j(将打印语句替换成为日志文件输出结果) 2.正式操作 1.基础传参 1.1创建方法,用于验证传参 1.2构建界面回显 1.3设置访问路径(localho…...
【算法日志】单调栈: 单调栈简介及其应用
代码随想录刷题60Day 目录 单调栈简介 单调栈的应用 下次更高温 下一个更大元素1 下一个更大元素2 接雨水 柱状图中最大矩形 单调栈简介 单调栈(Monotonic Stack)是一种特殊的栈数据结构,它满足元素的单调性,这种单调性需…...
VSCode自动分析代码的插件
今天来给大伙介绍一款非常好用的插件,它能够自动分析代码,并帮你完成代码的编写 效果如下图 首先我们用的是VSCode,(免费随便下) 找到扩展,搜索CodeGeeX,将它下载好,就可以实现了 到…...
设计模式之外观模式
文章目录 影院管理项目传统方式解决影院管理传统方式解决影院管理问题分析外观模式基本介绍外观模式原理类图外观模式解决影院管理传统方式解决影院管理说明外观模式应用实例 外观模式的注意事项和细节 影院管理项目 组建一个家庭影院: DVD 播放器、投影仪、自动屏…...
Web端测试和 App端测试有何不同?
Web 端测试和 App 端测试是针对不同平台的上的应用进行测试,Web应用和App端的应用实现方式不同,测试时的侧重点也不一样。 今天这篇文章就来介绍下两者的不同之处以及测试时的侧重点。 同时,我也准备了一份软件测试面试视频教程(…...
12.(Python数模)(相关性分析一)相关系数矩阵
相关系数矩阵 相关系数矩阵是用于衡量多个变量之间关系强度和方向的统计工具。它是一个对称矩阵,其中每个元素表示对应变量之间的相关系数。 要计算相关系数矩阵,首先需要计算每对变量之间的相关系数。常用的相关系数包括皮尔逊相关系数和斯皮尔曼相关…...
系统架构设计师(第二版)学习笔记----嵌入式系统及软件
【原文链接】系统架构设计师(第二版)学习笔记----嵌入式系统及软件 文章目录 一、嵌入式系统1.1 嵌入式系统的组成1.2 嵌入式系统的特点1.3 嵌入式系统的分类 二、嵌入式软件2.1 嵌入式系统软件分层2.2 嵌入式软件的主要特点 三、安全攸关软件的安全性设…...
Python列表操作指南:索引、切片、遍历与综合应用
文章目录 列表简介创建列表索引和切片列表的长度列表的拼接和重复检查元素是否存在列表的方法index() 方法count() 方法 列表的修改和删除修改元素删除元素列表的排序和反转添加元素 列表的拷贝列表的遍历列表的切片列表的嵌套列表推导式 python精品专栏推荐python基础知识&…...
第15章_锁: MySQL并发访问相同记录以及从数据操作的类型划分锁(读锁、写锁)
事务的 隔离性 由这章讲述的 锁 来实现。 1. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制. 在程序开发中会存在多线程同步的问题, 当多个线程并发访问某个数据的时候, 尤其是针对一些敏感数据(订单, 金额), 我们就需要保证这个数据在任何时刻最多只有一个线…...
PHP 排序函数使用方法,按照字母排序等操作
详解PHP排序方法使用 一、sort() 函数 用于对数组单元从低到高进行排序。 //数组 $data array(D,F,A,C,B); //排序 sort($data); //输出排版标签 echo "<pre>"; //打印数据 print_r($data);die;输出结果: 二、rsort() 函数 用于对数组单元从高到…...
windows本地验证码识别工具
windows本地验证码识别小工具 - 可以用在windows系统中,并可以集成在Java或python程序中 演示视频如下:可用于识别4-7位的字母数字组合的验证码(识别准确率在70% - 80%)。 验证码识别演示 本项目未开源,如需使用请联…...
修改图片尺寸的几个简单方法
修改图片尺寸的几个简单方法~~图片,是我们常用的文件格式,也是日常生活与工作中重要的文件。图片记录了非常多的元素和内容,其中不乏有工作上的内容,也有对一些日常生活的记录。所以说,图片文件对我们来说是非常重要的…...
三、GoLang字符串的基本操作
一、转义符是什么? 转义字符意义\n换行,将当前位置移动到下一行开头\r回车,将当前位置移到本行开头\t相当于一个Tab键\\代表一个反斜线“\”\"代表一个双引号字符 代码实战 package mainimport "fmt"/* *字符串基本用法 */ func main…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
Leetcode33( 搜索旋转排序数组)
题目表述 整数数组 nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前,nums 在预先未知的某个下标 k(0 < k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
Python第七周作业
Python第七周作业 文章目录 Python第七周作业 1.使用open以只读模式打开文件data.txt,并逐行打印内容 2.使用pathlib模块获取当前脚本的绝对路径,并创建logs目录(若不存在) 3.递归遍历目录data,输出所有.csv文件的路径…...
Yolo11改进策略:Block改进|FCM,特征互补映射模块|AAAI 2025|即插即用
1 论文信息 FBRT-YOLO(Faster and Better for Real-Time Aerial Image Detection)是由北京理工大学团队提出的专用于航拍图像实时目标检测的创新框架,发表于AAAI 2025。论文针对航拍场景中小目标检测的核心难题展开研究,重点解决…...
Ansible+Zabbix-agent2快速实现对多主机监控
ansible Ansible 是一款开源的自动化工具,用于配置管理(Configuration Management)、应用部署(Application Deployment)、任务自动化(Task Automation)和编排(Orchestration…...
