当前位置: 首页 > 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 镜像…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

基于PHP的连锁酒店管理系统

有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发&#xff0c;数据库mysql&#xff0c;前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...