C++STL(六)——list模拟
目录
- 本次所需实现的三个类
- 一、结点类的模拟实现
- 构造函数
- 二、迭代器类的模拟实现
- 为什么有迭代器类
- 迭代器类的模板参数说明
- 构造函数
- ++运算符的重载
- - -运算符的重载
- ==和!=运算符的重载
- *运算符的重载
- ->运算符的重载
- 引入模板第二个和第三个参数
- 三、list的模拟实现
- 3.1 默认成员函数
- 构造函数
- 拷贝构造函数
- 赋值运算符重载函数
- 析构函数
- 3.2 迭代器相关函数
- begin和end
- 3.3 访问容器相关函数
- front和back
- 3.4 插入、删除函数
- insert和erase
- push_back和pop_back
- push_front和pop_front
- 3.5 其他函数
- size
- clear
- empty
- swap
- 总结
本次所需实现的三个类
一、结点类的模拟实现
list类是由结点类和迭代器类组成
我们经常说list在底层实现时就是一个链表,更准确来说,list实际上是一个带头双向循环链表。

因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、前驱指针、后继指针)。
而对于该结点类的成员函数来说,我们只需实现一个构造函数即可。因为该结点类只需要根据数据来构造一个结点即可,而结点的释放则由list的析构函数来完成。
构造函数
template<class T> //由于该list可能是任何类型则使用模板类
struct list_node //存放结点成员变量
{list_node<T>* _prev;list_node<T>* _next;T _val;list_node(const T& val = T()) //缺省值初始化,T不一定是内置类型所以不能直接给0:_prev(nullptr), _next(nullptr), _val(val){}
};
注意: 若构造结点时没有传入数据,则默认以list容器所存储类型的默认构造函数所构造出来的值为传入数据。
二、迭代器类的模拟实现
引入:
list迭代器是一个自定义类型,内部成员是结点指针(内置类型),我们本身想要的就是结点指针,结点指针就可以做迭代器,它不能像原生指针(如:vector、string)一样地址是连续的而list地址不是,因为底层结构的差异,所以用一个类封装结点指针,然后重载运算符后就可以像内置类型一样访问。
为什么有迭代器类
在学习string和vector时都没有说专门要实现一个迭代器类,为什么实现list的时候就需要实现一个迭代器类呢?
因为string和vector对象都将其数据存储在了一块连续的内存空间,我们通过指针进行自增、自减以及解引用等操作,就可以对相应位置的数据进行一系列操作,因此string和vector当中的迭代器就是原生指针。

但是对于list来说,其各节点在内存中位置可能不都是连续的,大多情况下都是随机的,我们不能仅通过结点指针的自增、自减以及解引用等操作对相应结点的数据进行操作,需找到下一个结点才能访问。
迭代器的意义是让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。
既然list的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符操作进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器。比如使用list当中的迭代器进行自增操作时,实际上执行了node = node->next语句。
list迭代器类实际上就是对结点指针进行了封装,对其各种运算符进行了重载,使得结点指针的各种行为在使用者角度看起来和普通指针一样。
迭代器类的模板参数说明
list迭代器类的模板参数列表当中有三个模板参数
template<class T, class Ref, class Ptr>
在list的模拟实现当中,我们typedef重命名了两个迭代器类型,普通迭代器和const迭代器。
typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型。
构造函数
迭代器类实际上就是对结点指针进行了封装,其成员变量就只有一个,那就是结点指针,其构造函数直接根据所给结点指针构造一个迭代器对象。
Node* _node;__list_iterator(Node* node):_node(node)
{}
++运算符的重载
当然也分为前置和后置++
self& operator++() //返回类型还是迭代器
{_node = _node->_next; //下一个位置return *this;
}self operator++(int)
{self<T> 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;
}
==和!=运算符的重载
当使用==运算符比较两个迭代器时,实际上想知道的是这两个迭代器是否是同一个位置的迭代器,判断这两个迭代器当中的结点指针的指向是否相同即可。!=运算符则相反。
//通过结点的指针比较,两数据比较时end返回的数据具有常性要加const
bool operator!=(const self& it)
{return _node != it._node;
}
bool operator==(const self& it)
{return _node == it._node;
}
*运算符的重载
使用解引用操作符时,是想得到该位置的数据内容。因此,直接返回当前结点指针所指结点的数据即可,但是这里需要使用引用返回,因为解引用后可能需要对数据进行修改。
Ref operator*() //出了作用域还在可以引用返回
{return _node->_val; //结点指针的数据
}
->运算符的重载
有些情景下我们使用迭代器的时候可能会用到->运算符。
void test2()
{struct A{A(int a=0,int b=0):_a(a),_b(b){}int _a;int _b;};ling::list<A> lt;lt.push_back(A(1,1));lt.push_back(A(2,2));lt.push_back(A(3,3));lt.push_back(A(4,4));ling::list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << *it << " "; //遍历对象是自定义类型要重载流插入才可以打印//都能实现遍历//cout << (*it)._a << " "<<(*it)._b << endl; cout << it->_a << " " << it->_b << endl;++it;}cout << endl;
}
对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可
Ptr operator->()
{return &_node->_val;
}
引入模板第二个和第三个参数
如上例子:

三、list的模拟实现
3.1 默认成员函数
list是一个带头双向循环链表,在构造一个list对象时,申请一个头结点,并让其前驱指针和后继指针都指向自己

由于有时容易忘记写上显示实例化模板参数才构成类型
typedef list_node<T> node; //重命名一下
构造函数
list() //初始化构成双链表
{_head = new Node; //给头节点申请空间head->_prev = head; //前后指针指向自己head->_next = head;
}
拷贝构造函数
拷贝构造函数就是根据所给list容器,拷贝构造出一个对象。对于拷贝构造函数,先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面。
//拷贝构造函数
list(const list<T>& lt)
{_head = new node; //申请一个头结点_head->_next = _head; //头结点的后继指针指向自己_head->_prev = _head; //头结点的前驱指针指向自己for (const auto& e : lt) //记得引用{push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面}
}
赋值运算符重载函数
一般两种写法
//传统写法
list<T>& operator=(const list<T>& lt)
{if (this != <) //避免自己给自己赋值{clear(); //清空容器for (const auto& e : lt){push_back(e); //将容器lt当中的数据一个个尾插到链表后面}}return *this; //支持连续赋值
}//现代写法
list<T>& operator=(list<T> lt) //编译器接收右值的时候自动调用其拷贝构造函数
{swap(lt); //交换这两个对象return *this; //支持连续赋值
}
析构函数
对象进行析构时,首先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空
//析构函数
~list()
{clear(); //清理容器delete _head; //释放头结点_head = nullptr; //头指针置空
}
3.2 迭代器相关函数
begin和end
begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器。
list带头双向循环链表,第一个有效数据的迭代器就是使用头结点的下一个结点的地址构造出来的迭代器,最后一个有效数据的下一个位置的迭代器就是使用头结点的地址构造出来的迭代器。(最后一个结点的下一个结点就是头结点)
//单参数的构造函数支持隐式类型转换,两种写法都可以都是生成匿名对象
iterator begin()
{return _head->_next;//return iterator(_head->_next);
}
iterator end()
{return _head;//return iterator(_head);
}
3.3 访问容器相关函数
front和back
分别用于获取第一个有效数据和最后一个有效数据,因此,实现front和back函数时,直接返回第一个有效数据和最后一个有效数据的引用即可。
T& front()
{return *begin(); //返回第一个有效数据的引用
}
T& back()
{return *(--end()); //返回最后一个有效数据的引用
}//不可修改
const T& front() const
{return *begin(); //返回第一个有效数据的const引用
}
const T& back() const
{return *(--end()); //返回最后一个有效数据的const引用
}
3.4 插入、删除函数
insert和erase
当然还是任意位置插入和删除,由于会迭代器失效所以有返回值
//插入
iterator insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);prev->_next = newnode;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;
}//删除
iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return next;
}
有了这对增删函数就可以复用在其它增删的函数身上了
push_back和pop_back
分别对应尾插、尾删
//尾插
void push_back(const T& x)
{Node* tail = _head->prev; //找到尾Node* newnode = new Node(x); //调用结点的构造函数//更新尾结点tail->_next = newnode;newnode->_prev = tail;_head->_prev = newnode;newnode->_next = _head;//可直接复用insertinsert(end(),x);
}//尾删
void pop_back()
{erase(--end());
}
push_front和pop_front
//头插
void push_front(const T& x)
{insert(begin(), x);
}//头删
void pop_front()
{erase(begin());
}
3.5 其他函数
size
获取当前容器当中的有效数据个数,因为list是链表,所以只能通过遍历的方式逐个统计有效数据的个数。
size_t size()
{size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;
}
clear
用于清空容器,通过遍历的方式,逐个删除结点,只保留头结点
void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}
empty
用于判断容器是否为空,直接判断该容器的begin函数和end函数所返回的迭代器,是否是同一个位置的迭代器即可。(此时说明容器当中只有一个头结点)
bool empty() const
{return begin() == end(); //判断是否只有头结点
}
swap
用于交换两个容器,list容器当中存储的实际上就只有链表的头指针,将这两个容器当中的头指针交换即可。
void swap(list<T>& lt)
{std::swap(_head, lt._head); //交换两个容器当中的头指针即可
}
总结
由于list类不一定只接收一个类型如:内置类型(int、double),自定义类型,所以三个类都使用了模板。
类名+模板参数才构成类型容易忘记,往往typedef重命名一下它们也更方便使用。

相关文章:
C++STL(六)——list模拟
目录 本次所需实现的三个类一、结点类的模拟实现构造函数 二、迭代器类的模拟实现为什么有迭代器类迭代器类的模板参数说明构造函数运算符的重载- -运算符的重载和!运算符的重载*运算符的重载->运算符的重载引入模板第二个和第三个参数 三、list的模拟实现3.1 默认成员函数构…...
网络安全与AI:数字经济发展双引擎
在2025年年初,一场科技攻防战引发了全球关注。国产人工智能DeepSeek的爆火,伴随着大规模的网络攻击事件,将网络安全的重要性推上了风口浪尖。 在此背景下,我们计划探讨网络安全与人工智能如何为数字经济发展提供强大动力。网络安…...
WPS接入DeepSeek模型
1.wps 下载安装 WPS-支持多人在线协作编辑Word、Excel和PPT文档_WPS官方网站 (最好是安装最新的wps) 2.offieceAi工具下载安装 软件下载 | OfficeAI助手 下载后安装下载下来的两个工具。安装路径可以自行修改 3.打开WPS,点击文件-》 选项-》信任中心 勾…...
深度学习之神经网络框架搭建及模型优化
神经网络框架搭建及模型优化 目录 神经网络框架搭建及模型优化1 数据及配置1.1 配置1.2 数据1.3 函数导入1.4 数据函数1.5 数据打包 2 神经网络框架搭建2.1 框架确认2.2 函数搭建2.3 框架上传 3 模型优化3.1 函数理解3.2 训练模型和测试模型代码 4 最终代码测试4.1 SGD优化算法…...
采用分步式无线控制架构实现水池液位自动化管理
以下是基于巨控GRM241Q-4D4I4QHE模块的完整技术方案,采用分步式无线控制架构实现水池液位自动化管理: 一、系统架构设计 硬件部署 山顶单元 GRM241Q模块(带4G功能) 液位计(4-20mA) 功能:实时采…...
OpenEuler学习笔记(二十三):在OpenEuler上部署开源MES系统
在OpenEuler上部署小企业开源MES(制造执行系统,Manufacturing Execution System)是一个非常有价值的项目,可以帮助企业实现生产过程的数字化管理。以下是基于开源MES系统(如 Odoo MES 或 OpenMES)的部署步骤…...
SpringSecurity:授权服务器与客户端应用(入门案例)
文章目录 一、需求概述二、基本授权登录功能实现1、授权服务器开发2、客户端开发3、功能测试 三、自定义授权服务器登录页1、授权服务器开发2、功能测试 四、自定义授权服务器授权页1、授权服务器开发2、功能测试 五、客户端信息保存数据库1、授权服务器开发2、功能测试 一、需…...
没用的文章又➕1
次次登陆GitHub都让我抓心挠肝,用了热度最高的法子也不抵事儿。谁说github上全是大神了,也要有我这样的小菜鸟。下面是我的失败记录… 查询目标网站的DNS 在whois上输入目标网站github.com,在查询结果当中选取任意一个DNS将地址和名称添加在…...
BiGRU双向门控循环单元多变量多步预测,光伏功率预测(Matlab完整源码和数据)
代码地址:BiGRU双向门控循环单元多变量多步预测,光伏功率预测(Matlab完整源码和数据) BiGRU双向门控循环单元多变量多步预测,光伏功率预测 一、引言 1.1、研究背景和意义 随着全球对可再生能源需求的不断增长,光伏…...
谷歌浏览器多开指南:如何完成独立IP隔离?
对于跨境电商来说,在进行社交媒体营销、广告投放等业务活动时,往往需要同时登录多个账号来提高运营效率和提升营销效果。然而,如果这些账号共享相同的 IP 地址,很容易被平台检测为关联账号,进而触发安全验证甚至封禁。…...
Django开发入门 – 3.用Django创建一个Web项目
Django开发入门 – 3.用Django创建一个Web项目 Build A Web Based Project With Django By JacksonML 本文简要介绍如何利用最新版Python 3.13.2来搭建Django环境,以及创建第一个Django Web应用项目,并能够运行Django Web服务器。 创建该Django项目需…...
【Java】多线程和高并发编程(三):锁(下)深入ReentrantReadWriteLock
文章目录 4、深入ReentrantReadWriteLock4.1 为什么要出现读写锁4.2 读写锁的实现原理4.3 写锁分析4.3.1 写锁加锁流程概述4.3.2 写锁加锁源码分析4.3.3 写锁释放锁流程概述&释放锁源码 4.4 读锁分析4.4.1 读锁加锁流程概述4.4.1.1 基础读锁流程4.4.1.2 读锁重入流程4.4.1.…...
讲解ES6中的变量和对象的解构赋值
在 ES6 中,解构赋值是一种非常方便的语法,它使得从数组或对象中提取值变得更加简洁和直观。解构赋值支持变量赋值,可以通过单独提取数组或对象的元素来赋值给变量。 下面我将分别讲解 数组解构 和 对象解构 的基本用法和一些高级特性。 1. …...
DeepSeek Coder + IDEA 辅助开发工具
开发者工具 我之前用的是Codegeex4模型,现在写一款DeepSeek Coder 本地模型 DeepSeek为什么火,我在网上看到一个段子下棋DeepSeek用兵法赢了ChatGpt,而没有用技术赢,这就是AI的思维推理,深入理解孙子兵法,…...
云计算——AWS Solutions Architect – Associate(saa)4.安全组和NACL
安全组一充当虚拟防火墙对于关联实例,在实例级别控制入站和出站流量。 网络访问控制列表(NACL)一充当防火墙关联子网,在子网级别控制入站和出站流量。 在专有网络中,安全组和网络ACL(NACL)一起帮助构建分层网络防御。 安全组在实例级别操作…...
动量+均线组合策略关键点
动量均线组合策略关键点: 趋势确认: MA系统判断主趋势方向动量指标判断趋势强度 入场条件: 价格站上重要均线(如20日线)动量指标向上并保持高位短期均线上穿长期均线 出场条件: 价格跌破均线系统动量指标见顶回落短期均线下…...
Blazor-<select>
今天我们来说说<select>标签的用法,我们还是从一个示例代码开始 page "/demoPage" rendermode InteractiveAuto inject ILogger<InjectPage> logger; <h3>demoPage</h3> <select multiple>foreach (var item in list){<…...
Synchronized使用
文章目录 synchronized使用基本概念使用方法实现原理锁的粒度并发编程注意事项与Lock锁对比比较线程安全性与性能 synchronized使用 当涉及到多线程编程时,保证数据的正确性和一致性是至关重要的。而synchronized关键字是Java语言中最基本的同步机制之一࿰…...
OpenStack四种创建虚拟机的方式
实例(Instances)是在云内部运行的虚拟机。您可以从以下来源启动实例: 一、上传到镜像服务的镜像(Image) 使用已上传到镜像服务的镜像来启动实例。 二、复制到持久化卷的镜像(Volume) 使用已…...
Expo运行模拟器失败错误解决(xcrun simctl )
根据你的描述,问题主要涉及两个方面:xcrun simctl 错误和 Expo 依赖版本不兼容。以下是针对这两个问题的解决方案: 解决 xcrun simctl 错误 错误代码 72 通常表明 simctl 工具未正确配置或路径未正确设置。以下是解决步骤: 确保 …...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
黑马Mybatis
Mybatis 表现层:页面展示 业务层:逻辑处理 持久层:持久数据化保存 在这里插入图片描述 Mybatis快速入门  协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
