当前位置: 首页 > news >正文

yo!这里是STL::list类简单模拟实现

目录

前言

重要接口实现

框架

默认成员函数

迭代器(重点)

1.引言

2.list迭代器类实现 

3.list类中调用实现 

增删查改

后记


前言

        我们知道,stl中的vector对应数据结构中的顺序表,string类对应字符串,而今天要讲的list类对应带头双向链表,并不是对应单链表,带头双向链表的基本操作在数据结构课程中已经学过,所以今天即将要讲的常见接口并不是重点,重点是list的迭代器的实现。

        我们也知道,string、vector的迭代器就是原生指针,如果使用原生指针可以实现list的迭代器吗?答案是不行,因为list的数据并不是一块连续的存储空间,无法像指针那样取访问元素,但是为了保持所有容器迭代器的使用一致,我们如何实现list的迭代器才能像原生指针那样通过++、--去控制,这里就体现出了封装的重要性,快往下看看吧!

重要接口实现

  • 框架

        通过下方代码可以看出,实现了一个节点类模板存储节点,一个链表类模板存储链表,

①使用类模板是因为存储元素可以自由指定,不是像以前一样通过typedef固定了每个链表的元素类型;

②节点类模板使用struct,而不使用class,是因为struct的默认权限是public,链表类模板可以自由访问其成员变量,而class默认权限是private,当然用class指定public权限也可以,

节点类模板中通过构造函数初始化元素,链表类模板成员是一个节点指针,可以在构造函数中申请一个节点充当头节点。

代码:

template <class T>
struct ListNode
{//构造函数用来创节点ListNode(const T& x = T()):_data(x), _prev(nullptr), _next(nullptr){}T _data;ListNode<T>* _prev;ListNode<T>* _next;
};template <class T>
class List
{typedef ListNode<T> Lnode;  //作用:下面写ListNode<T>较麻烦,typedef一下,使用Lnode较方便public://...private:Lnode* _head;
};
  • 默认成员函数

        因为在所有构造函数前都需要先初始化成员变量(为头节点申请空间并将左右指针置空),所以封装了一个函数empty_init在每个构造函数前直接调用,

        所有默认成员函数的实现与string、vector中的如出一辙,不太理解的可以参考一下之前的文章,不是重点这里不再赘述。

代码:

	//创建并初始化头节点,放于构造函数的最前面void empty_init(){_head = new Lnode();_head->_next = _head->_prev = _head;}//构造函数List(){empty_init();}//普通拷贝构造函数List(const List& L)   //注意:类外必须是List<类型名>,类里可以是List,但建议List<T>{empty_init();auto it = L.begin();while (it != L.end()){push_back(*it);it++;}}void swap(List<T>& L){std::swap(_head, L._head);}//传迭代器区间构造函数template <class InputIterator>  List(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);++first;}}//拷贝构造函数//现代写法List(const List<T>& L){empty_init();List<T> tmp(L.begin(), L.end());swap(tmp);}//赋值运算符重载//现代写法List<T>& operator=(const List<T>& L){List<T> tmp(L.begin(), L.end());swap(tmp);return *this;}//更狠的现代写法List<T>& operator=(List<T> L)   //直接传一个拷贝过来,相当于上面的tmp,函数结束自动释放{swap(L);return *this;}//清除除了头节点之外所有的节点void clear(){auto it = begin();while (it != end()){it = erase(it);}}//析构函数~List(){clear();_head->_next = _head->_prev = nullptr;delete _head;_head = nullptr;}
  • 迭代器(重点)

1.引言

        还记得vector、string的迭代器实现吗?typedef T* iterator;   typedef const T* const_iterator;   ,只是原生指针对不对,然后typedef一下,就可以使用了,因为指针可以在一块连续的地址空间++或--访问元素,但是链表中的节点指针++、--访问元素可以吗,答案是不可以,但为了保持迭代器使用一致,就应该遇到链表的迭代器使用++、--也可以达到一样的效果,因此就想到了操作符重载,将++、--、*  重载成我们希望达到的效果,而实现操作符重载就必须将其封装成一个类。

        从引言中就可以看出list与之前学过的容器迭代器实现的不同之处,list迭代器是通过封装成类实现,但迭代器有两种(暂时先不提反向迭代器),一种普通迭代器,一种const迭代器,两种迭代器的实现应该大致内容都一样,小部分不一样(比如,const迭代器的解引用应该返回const不可类型的变量),那我们应该先写好普通迭代器的实现,再复制粘贴成const迭代器然后修修改改吗?漏!大漏特漏!接触过模板这个概念之后,应该可以想到这里用到模板。

2.list迭代器类实现 

1)框架 

        见下方代码, 实现list的迭代器这个__list_iterator类模板,参数列表中的T、Ref、Ptr分别是数据类型、此类型的引用、此类型的指针(比如,T是int,Ref就是int&,Ptr就是int*)(为什么需要指针参数在操作符->重载处有说明),填入的参数不同就是不同的类,这里list的迭代器需要两个类,一个普通迭代器的类,一个const迭代器的类,在list类实现中去定义即可。

        针对list迭代器类模板的实现,成员变量是节点的指针,而构造函数则是传入一个节点指针可初始化一个list迭代器,不需要自己提供析构函数。

代码:

template <class T, class Ref, class Ptr>
struct __list_iterator
{//注意:这两个typedef只是因为ListNode<T>、__list_iterator<T, Ref, Ptr>很麻烦写,所以简化一下,也方便理解typedef ListNode<T> Lnode;typedef __list_iterator<T, Ref, Ptr> iterator;//构造函数__list_iterator(Lnode* p):_node(p){}//...Lnode* _node;   //链表中的迭代器表示节点的指针
};

2) 关系运算符重载

        判断两个迭代器相不相等即判断作为成员变量的结点指针是不是同一个指针变量,很容易理解,加上const是无论普通list对象还是const list对象都可以调用。

代码:

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

 3)运算符++、--重载

        针对list类,迭代器的++就是访问下一个节点的迭代器,--是访问上一个节点的迭代器,再注意好前置与后置的实现即可,不是很难。

代码:

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

4)操作符*重载

        list类中的迭代器解引用就是访问此节点的数据,并且返回引用类型,即可操作其中的值,普通迭代器的Ref是普通引用类型,即可读可写,const迭代器的Ref是const引用类型,只可读不可写。

        注意:不需要将此成员函数设置成const类型,就像本作者初学时所疑惑的,如果Ref是const T&,不应该对应const成员函数(Ref operator*() const)吗?其实不然,list类的迭代器使用了类模板,参数不同就是不同的类,直接将两种迭代器分开了,如果是普通迭代器,Ref就会传进来T&,调用解引用重载时返回引用,可读可写,如果是const迭代器,Ref就会传进来const T&,调用解引用重载时返回const引用,只可读不可写。

代码:

	Ref operator*(){return _node->_data;}

 5)操作符->重载

        正常情况下,操作符->可以解引用结构体指针再取其成员,那对于底层是节点指针的list迭代器,->就是对迭代器解引用再取其成员,所以针对_data如果是个自定义类型,那么->就可以取其成员,比如,_data是个自定义类型POS,有两个成员,一个x,一个y,那么it->x,it->y表示迭代器取的POS中的x、y,如下图测试,

        仔细观察->操作符重载的实现,it->x应该写成it->->x才是对的,因为it->返回自定义类型指针,再->x返回其成员,这里其实编译器做了处理,省略了一个->,提高了可读性。

        这里的Ptr就是数据类型的指针变量,将其地址返回,与引用形式一样,需要从类模板参数列表输入。

代码:

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

 测试:

3.list类中调用实现 

        在实现完__list_iterator这个list类迭代器类模板之后,通过在list类中输入不同的模板参数定义不同的类,这里需要普通迭代器类和const迭代器类,如下代码,begin()是返回首元节点的迭代器,而end()是返回最后一个元素节点的下一个位置迭代器,即头节点迭代器。

 代码:

    typedef __list_iterator<T, T&, T*> iterator;  //普通迭代器类typedef __list_iterator<T, const T&, const T*> const_iterator;   //const迭代器类iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}
  • 增删查改

        在理解了list的迭代器实现之后,对list的增删改查想必是游刃有余,这里实现一下基本的插入删除操作,结合之前在数据结构中的知识,insert、erase的实现应该可以很快写出,值得注意的是,insert返回新插入节点的迭代器,erase返回所删节点的下一位置的迭代器,而尾插、尾删、头插、头删直接复用即可。

代码:

	iterator insert(iterator pos, const T& x){Lnode* newNode = new Lnode(x);pos._node->_prev->_next = newNode;newNode->_prev = pos._node->_prev;newNode->_next = pos._node;pos._node->_prev = newNode;return iterator(newNode);  //返回插入位置的迭代器}//尾插void push_back(const T& x){insert(end(), x);}//头插void push_front(const T& x){insert(begin(), x);}iterator erase(iterator pos){assert(pos != end());Lnode* tmp = pos._node->_next;pos._node->_prev->_next = pos._node->_next;pos._node->_next->_prev = pos._node->_prev;delete pos._node;return iterator(tmp);}//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}

后记

        在list类的实现中,默认成员函数、操作符重载以及增删查改已不再是重点,重点是掌握迭代器的实现,因为与string、vector中的迭代器实现不同,也没有那么简单,上面总结的很清楚,不懂的可以私我或者写在评论区,加油,拜拜!


相关文章:

yo!这里是STL::list类简单模拟实现

目录 前言 重要接口实现 框架 默认成员函数 迭代器&#xff08;重点&#xff09; 1.引言 2.list迭代器类实现 3.list类中调用实现 增删查改 后记 前言 我们知道&#xff0c;stl中的vector对应数据结构中的顺序表&#xff0c;string类对应字符串&#xff0c;而今天要…...

小程序商城开发制作

当开发一个商城小程序时&#xff0c;费用是一个非常重要的考虑因素。然而&#xff0c;准确回答这个问题是有一定困难的&#xff0c;因为开发商城小程序的费用取决于多个因素。以下是一些可能影响价格的主要因素&#xff1a; 1. 功能需求&#xff1a;商城小程序的复杂程度和功能…...

并发编程面试题2

并发编程面试题2 一、AQS高频问题&#xff1a; 1.1 AQS是什么&#xff1f; AQS就是一个抽象队列同步器&#xff0c;abstract queued sychronizer&#xff0c;本质就是一个抽象类。 AQS中有一个核心属性state&#xff0c;其次还有一个双向链表以及一个单项链表。 首先state…...

关于eclipse导入部署具有增删改查的项目

目录 前言&#xff1a;当我们刚刚进入公司的第一步就是去部署当前公司的项目&#xff0c;本博客就是详细介绍怎么去部署当前公司的项目。 一&#xff0c;开发工具&#xff1a; 二&#xff0c;具体步骤&#xff1a; 2.1导入公司的项目 打开eclipse开发工具 2.2配置当前的环…...

c++日志工具之——log4cpp

1、log4cpp概述 Log4cpp是一个开源的C类库&#xff0c;它提供了C程序中使用日志和跟踪调试的功能&#xff0c;它的优点如下&#xff1a; 提供应用程序运行上下文&#xff0c;方便跟踪调试&#xff1b; 可扩展的、多种方式记录日志&#xff0c;包括命令行、文件、回卷文件、内…...

ES索引重建reindex详解

目录 一、使用场景 二、reindex介绍 三、使用手册 1、覆盖更新 2、创建丢失的文档并更新旧版本的文档 3、仅创建丢失的文档 4、冲突处理 5、source中添加查询条件 6、source中包含多个源索引 7、限制处理的记录数 8、从远程ES集群中重建索引 9、提取随机子集 10、…...

前沿分享-中距离射频取电

目前来看&#xff0c;微能源有四种技术路线&#xff0c;一是环境光采集、温差转换采集、无线射频采集和振动能量采集。 无线射频微能源是在通信设备通信过程中自然产生的&#xff0c;可以通过射频能量芯片实现无线射频取电&#xff0c;能瞬间大功率储电和安全驱动负载。 通过射…...

UnrealEngine - 网络同步之连接篇

1 连接过程 - 握手 传统的 C/S 架构下&#xff0c;Client 和 Server 通常会建立一条抽象的 Connection&#xff0c;用来进行两端的通信。 UE 的官方文档中提供了 Client 连接到 Server 的示例 &#xff0c;简单来说分为如下几步&#xff1a; 打包构建好 Client 和 Server 进程…...

【JDBC系列】- 扩展提升学习

扩展提升学习 &#x1f604;生命不息&#xff0c;写作不止 &#x1f525; 继续踏上学习之路&#xff0c;学之分享笔记 &#x1f44a; 总有一天我也能像各位大佬一样 &#x1f3c6; 博客首页 怒放吧德德 To记录领地 &#x1f31d;分享学习心得&#xff0c;欢迎指正&#xff0…...

阻塞和非阻塞,同步和异步

文章目录 典型的一次IO的两个阶段IO多路复用是同步还是异步&#xff1f; 典型的一次IO的两个阶段 数据就绪和数据读写 同步&#xff1a;需要应用程序自己操作 IO多路复用是同步还是异步&#xff1f; epoll也是同步的 具体数据读取还是通过应用程序自己完成的 只有使用了特…...

提速Rust编译器!

Nethercote是一位研究Rust编译器的软件工程师。最近&#xff0c;他正在探索如何提升Rust编译器的性能&#xff0c;在他的博客文章中介绍了Rust编译器是如何将代码分割成代码生成单元&#xff08;CGU&#xff09;的以及rustc的性能加速。 他解释了不同数量和大小的CGU之间的权衡…...

QT创建项目

可选择CMake或qmake...

基于vue3+webpack5+qiankun实现微前端

一 主应用改造&#xff08;又称基座改造&#xff09; 1 在主应用中安装qiankun(npm i qiankun -S) 2 在src下新建micro-app.js文件&#xff0c;用于存放所有子应用。 const microApps [// 当匹配到activeRule 的时候&#xff0c;请求获取entry资源&#xff0c;渲染到containe…...

华为OD真题--完美走位--带答案

2023华为OD统一考试&#xff08;AB卷&#xff09;题库清单-带答案&#xff08;持续更新&#xff09;or2023年华为OD真题机考题库大全-带答案&#xff08;持续更新&#xff09; 题目描述 输入一个长度为4的倍数的字符串Q,字符串中仅包含WASD四个字母。 将这个字符串中的连续子串…...

【AI】《动手学-深度学习-PyTorch版》笔记(十四):多层感知机

AI学习目录汇总 1、多层感知机网络结构 1.1 线性模型:softmax回归 在前面介绍过,使用softmax回归来处理分类问题时,每个输出通过都一个仿射函数计算,网络结构如下,输入和输出之间为全链接层: 1.2 多层感知机 多层感知机就是在输入和输出中间再添加一个或多个全链接…...

本地开发 npm 好用的http server、好用的web server、静态服务器

好用的web server总结 有时需要快速启动一个web 服务器&#xff08;http服务器&#xff09;来伺服静态网页&#xff0c;安装nginx又太繁琐&#xff0c;那么可以考虑使用npm serve、http-server、webpack-dev-server。 npm serve npm 的serve可以提供给http server功能&#…...

Gradio入门,并搭个鸡兔同笼问题小应用,附源码(MindOpt)

应用链接&#xff1a; https://979427749bc9ceec34.gradio.live 是公开访问链接&#xff0c;3天有效。 在modelscope中的创空间发布长期有效:https://modelscope.cn/studios/wuyoy520v01/MindOpt_Chicken-with-rabbit-cage/summary。 应用图如下&#xff0c;源代码见正文。 知…...

redis核心知识点简略笔记

value数据类型 string 二进制安全 list 有序、可重复 set 无序、不重复 hash field-value的map sorted set 不重复、通过double类型score分数排序 场景 string 计数器缓存分布式锁访问频率控制分布式session hash 购物车等对象属性灵活修改 list 定时排行榜 set 收藏 sorte…...

消息中间件 —— 初识Kafka

文章目录 1、Kafka简介1.1、消息队列1.1.1、为什么要有消息队列&#xff1f;1.1.2、消息队列1.1.3、消息队列的分类1.1.4、p2p 和 发布订阅MQ的比较1.1.5、消息系统的使用场景1.1.6、常见的消息系统 1.2、Kafka简介1.2.1、简介1.2.2、设计目标1.2.3、kafka核心的概念 2、Kafka的…...

Ceph集群安装部署

Ceph集群安装部署 目录 Ceph集群安装部署 1、环境准备 1.1 环境简介1.2 配置hosts解析(所有节点)1.3 配置时间同步2、安装docker(所有节点)3、配置镜像 3.1 下载ceph镜像(所有节点执行)3.2 搭建制作本地仓库(ceph-01节点执行)3.3 配置私有仓库(所有节点执行)3.4 为 Docker 镜像…...

【代码】基于交替方向乘子法(admm)的微电网分布式低碳优化运行策略matlab-yalmip-cplex/gurobi

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f447; 关注我领取海量matlab电子书和…...

Orwell Dev-C++和Embarcadero Dev-C++哪个更稳定

关于Orwell Dev-C和Embarcadero Dev-C的稳定性比较&#xff0c;可以从以下几个维度分析&#xff1a;开发背景Orwell Dev-C&#xff08;5.x版本&#xff09;是原始Dev-C项目的延续维护版本&#xff0c;最后一次更新为2015年&#xff1b;Embarcadero Dev-C&#xff08;6.x版本&am…...

AGI情感可信度认证体系(ISO/IEC 23894-3:2024中国落地首测实录)

第一章&#xff1a;AGI情感可信度认证体系的范式跃迁 2026奇点智能技术大会(https://ml-summit.org) 传统AI伦理评估框架长期依赖静态规则库与人工标注反馈&#xff0c;难以应对AGI在开放语境中动态生成共情表达、自我修正情绪表征及跨文化情感适配等高阶能力。情感可信度不再…...

可观测性Observability三大支柱:指标Metrics、日志Logs、追踪Trace介绍(通过系统外部输出,推断系统内部状态能力)全链路路径、Span跨度、OpenTelemetry、性能监控

文章目录可观测性三大支柱&#xff1a;Metrics、Logs、Traces 全面解析一、什么是可观测性&#xff1f;二、Metrics&#xff08;指标&#xff09;&#xff1a;系统“体征”1. 什么是 Metrics&#xff1f;2. Metrics 的特点3. 常见类型4. 使用场景5. 示例三、Logs&#xff08;日…...

从《未来编年史》到现实:聊聊火星移民、世界政府与‘灵性科技’的百年预言靠谱吗?

火星移民、全球治理与意识科技&#xff1a;未来预言的现实可行性拆解 当SpaceX的星舰原型机在得克萨斯州的试验场轰然升空时&#xff0c;埃隆马斯克那句"让人类成为多行星物种"的宣言再次引发热议。与此同时&#xff0c;Neuralink的脑机接口技术已在人体试验中取得进…...

从资源死锁到高效协同:深入解析Volcano调度器如何重塑K8s批处理任务调度

1. 当K8s遇上批处理任务&#xff1a;为什么原生调度器会"卡死"&#xff1f; 去年我在给一家AI公司做技术咨询时&#xff0c;遇到一个典型场景&#xff1a;他们的GPU集群总出现"部分Worker启动&#xff0c;整个训练任务卡住"的情况。具体表现是&#xff0c;…...

从Emoji到图标库:给你的Markdown文档加点‘颜’和‘料’(附Font Awesome/Octicons使用指南)

从Emoji到图标库&#xff1a;给你的Markdown文档加点‘颜’和‘料’&#xff08;附Font Awesome/Octicons使用指南&#xff09; 在技术文档的世界里&#xff0c;文字是骨架&#xff0c;而视觉元素则是让文档活起来的血肉。当Unicode Emoji已经无法满足你对文档美学的追求时&…...

手把手教你用Simulink搭建Buck变换器:从元器件选型到波形分析

手把手教你用Simulink搭建Buck变换器&#xff1a;从元器件选型到波形分析 在电力电子领域&#xff0c;Buck变换器作为最基础的DC-DC降压拓扑&#xff0c;几乎出现在所有电源设计工程师的入门课程中。但很多初学者在理论学习后&#xff0c;面对实际仿真建模时仍会感到无从下手—…...

实战构建抖音直播弹幕采集系统:DouyinLiveWebFetcher技术实现方案

实战构建抖音直播弹幕采集系统&#xff1a;DouyinLiveWebFetcher技术实现方案 【免费下载链接】DouyinLiveWebFetcher 抖音直播间网页版的弹幕数据抓取&#xff08;2025最新版本&#xff09; 项目地址: https://gitcode.com/gh_mirrors/do/DouyinLiveWebFetcher 在社交媒…...

从奇偶校验到矩阵修复:布尔矩阵的奇偶均势特性解析

1. 布尔矩阵的奇偶校验&#xff1a;从概念到实践 第一次接触布尔矩阵的奇偶校验问题时&#xff0c;我盯着那个4x4的矩阵样例看了足足十分钟。那些0和1的排列看似随机&#xff0c;却隐藏着某种神秘的对称性——这就是所谓的"奇偶均势特性"。简单来说&#xff0c;这个特…...