【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…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...
