【C++】STL-list模拟实现
目录
1、本次需要实现的3个类即接口总览
2、list的模拟实现
2.1 链表结点的设置以及初始化
2.2 链表的迭代器
2.3 容量接口及默认成员函数
1、本次需要实现的3个类即接口总览
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct __List_node//创建一个T类型的链表结点
{__List_node(const T& data = T());//构造函数__List_node<T>* _next;__List_node<T>* _prev;T _data;
};
template<class T, class Ref, class Ptr>
struct __List_iterator//封装链表的迭代器
{typedef __List_node<T> Node;typedef __List_iterator<T, Ref, Ptr> Self;Node* _node;//成员变量__List_iterator(Node* node);//构造函数。将迭代器中的结点初始化成传过来的结点//各种运算符重载函数Ref operator*();Ptr operator->();Self& operator++();Self operator++(int);Self& operator--();Self operator--(int);bool operator!=(const Self& it);
};
template<class T>
class List//真正的链表
{
public:typedef __List_node<T> Node;//将链表结点的名称重命名为Nodetypedef __List_iterator<T, T&, T*> iterator;typedef __List_iterator<T, const T&, const T*> const_iterator;//带头双向循环链表//默认成员函数List();~List();List(const List<T>& lt);List<T>& operator=(List<T> lt);//插入删除函数void clear();//clear是清除除了头节点意外的所以结点void push_back(const T& x);//一定要用引用,因为T不一定是内置类型void pop_back();void push_front(const T& x);void pop_front();void insert(iterator pos, const T& x);void erase(iterator pos);//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;
private:Node* _head;
};
2、list的模拟实现
2.1 链表结点的设置以及初始化
list是双向带头循环链表,所以链表的结点的成员变量需要有一个数值,一个指向前一个结点的指针,一个指向后一个结点的指针。初始化时,需要创建一个不存放有效值的头节点,并让头节点的两个指针都指向自己
链表的成员变量只需要一个指向头节点的指针
结点的结构体当中也需要创建一个构造函数,因为在创建结点时可以传入值
template<class T>
struct __List_node//创建一个T类型的链表结点
{__List_node(const T& data = T())//构造函数:_data(data), _next(nullptr), _prev(nullptr){}__List_node<T>* _next;__List_node<T>* _prev;T _data;
};
List()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}
上面的构造函数一定要使用引用,因为T不一定是内置类型
2.2 链表的迭代器
在上面的总览中可以看到链表的迭代器倍封装成了一个类,并且这个类有3个参数
首先,解释为什么会倍封装成一个类呢?
在vector中,迭代器就是一个指针,当我们对这个指针解引用(即*),就可以拿到这个指针所指向的数据,对其++,就可以让指针往下一个数据走,但在list中不行。如果迭代器是指向一个结点的指针,那么当对这个指针解引用时,拿到的是一个类对象,即这个结点本身,并不能拿到其中的数据,当对这个指针++时,并不能往下一个结点走所以我们需要将迭代器封装成一个类,这个类中的成员变量仍然是一个指向结点的指针,只是我们会重载一下里面的运算符,让我们*或++等操作的时候,能够直接拿到里面的数据和让指针往下一个结点。所以我们封装这个类的原因,就是为了让我们在使用list时,与使用vector等是一样的,即更加方便。实际上,迭代器这个类里面的成员变量仍然是一个指向结点的指针。
其次,解释为什么会有3个参数呢?
我们可以看到在链表类中会对迭代器进行重命名
typedef __List_iterator<T, T&, T*> iterator;
typedef __List_iterator<T, const T&, const T*> const_iterator;
因为我们对于list和const list调用的迭代器是不同的,若我们只有一个参数T,那这个时候我们重命名是没办法重命名两个的,也就是说,若只有一个参数,则需要封装两个迭代器的类,而这两个类中只有operator*和operator->是不同的,所以弄成3个参数会更好一些。
template<class T, class Ref, class Ptr>
struct __List_iterator//封装链表的迭代器
{typedef __List_node<T> Node;typedef __List_iterator<T, Ref, Ptr> Self;Node* _node;//成员变量__List_iterator(Node* node)//构造函数。将迭代器中的结点初始化成传过来的结点:_node(node){}// *itRef operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}// ++itSelf& operator++(){_node = _node->_next;return *this;}// it++Self operator++(int){Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的//_node = _node->_next;++(*this);return tmp;}// --itSelf& operator--(){_node = _node->_prev;return *this;}// it--Self operator--(int){Self tmp(*this);//_node = _node->_prev;--(*this);return tmp;}// it != end()bool operator!=(const Self& it){return _node != it._node;}
};
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调用begin是,则会调用返回值是iterator的,而iterator是__List_iterator<T, T&, T*>的,也就是调用*和->时,拿到的都是可读可写的值,反之则是只读的
int main()
{//对于内置类型List<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);List<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}return 0;
}
class Date
{
public:int _year = 0;int _month = 1;int _day = 1;
};
int main()
{//对于自定义类型List<Date> lt;lt.push_back(Date());lt.push_back(Date());List<Date>::iterator it = lt.begin();while (it != lt.end()){cout << it->_year << " " << it->_month << " " << it->_day << endl;//也可以cout << (*it)._year << " " << (*it)._month << " " << (*it)._day << endl;it++;}return 0;
}
->通常是在链表中存储的是自定义类型才会使用,通过上面可知->返回的是这个结构体的数值域的地址,那不应该是it->->_year吗(因为前面的it->返回后是Date*)?为了可读性,倍编译器处理了一下
这里说明一下begin和end返回的结点分别是那个

2.3 容量接口及默认成员函数
~List()
{clear();delete _head;_head = nullptr;
}
List(const List<T>& lt)
{_head = new Node;_head->_next = _head;_head->_prev = _head;//const_iterator it = lt.begin();//这里迭代器不需要指定是那个类域,因为就是在这个类中使用//while (it != lt.end())//{// push_back(*it);// ++it;//}for (auto e : lt)//这里与上面用迭代器一样,因为最终也会被替换成迭代器push_back(e);
}
/*List<T>& operator=(const List<T>& lt)
{if (this != <){clear();for (ayto e : lt)push_back(e);}return *this;
}*/
List<T>& operator=(List<T> lt)
{swap(_head, lt._head);//原来的空间给这个临时变量,因为这个临时变量是自定义类型,出了作用域后会自动调用析构函数return *this;
}
void clear()//clear是清除除了头节点意外的所以结点
{iterator it = begin();while (it != end()){erase(it++);}
}
void push_back(const T& x)//一定要用引用,因为T不一定是内置类型
{Node* tail = _head->_prev;Node* newnode = new Node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;/*insert(end(),x);*/
}
void pop_back()
{Node* tail = _head->_prev;Node* prev = tail->_prev;delete tail;_head->_prev = prev;prev->_next = _head;//erase(--end());
}
void push_front(const T& x)
{Node* first = _head->_next;Node* newnode = new Node(x);_head->_next = newnode;newnode->_prev = _head;newnode->_next = first;first->_prev = newnode;//insert(begin(), x);
}
void pop_front()
{Node* first = _head->_next;Node* second = first->_next;delete first;_head->_next = second;second->_prev = _head;//erase(begin());
}
void insert(iterator pos, const T& x)
{Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}
void 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;cur = nullptr;
}
template<class T>
struct __List_node//创建一个T类型的链表结点
{__List_node(const T& data = T())//构造函数:_data(data), _next(nullptr), _prev(nullptr){}__List_node<T>* _next;__List_node<T>* _prev;T _data;
};
template<class T, class Ref, class Ptr>
struct __List_iterator//封装链表的迭代器
{typedef __List_node<T> Node;typedef __List_iterator<T, Ref, Ptr> Self;Node* _node;//成员变量__List_iterator(Node* node)//构造函数。将迭代器中的结点初始化成传过来的结点:_node(node){}// *itRef operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}// ++itSelf& operator++(){_node = _node->_next;return *this;}// it++Self operator++(int){Self tmp(*this);//调用默认的拷贝构造,因为是指针类型所以直接用默认的//_node = _node->_next;++(*this);return tmp;}// --itSelf& operator--(){_node = _node->_prev;return *this;}// it--Self operator--(int){Self tmp(*this);//_node = _node->_prev;--(*this);return tmp;}// it != end()bool operator!=(const Self& it){return _node != it._node;}
};
template<class T>
class List//真正的链表
{
public:typedef __List_node<T> Node;//将链表结点的名称重命名为Nodetypedef __List_iterator<T, T&, T*> iterator;typedef __List_iterator<T, const T&, const T*> const_iterator;//带头双向循环链表List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}~List(){clear();delete _head;_head = nullptr;}List(const List<T>& lt){_head = new Node;_head->_next = _head;_head->_prev = _head;//const_iterator it = lt.begin();//这里迭代器不需要指定是那个类域,因为就是在这个类中使用//while (it != lt.end())//{// push_back(*it);// ++it;//}for (auto e : lt)//这里与上面用迭代器一样,因为最终也会被替换成迭代器push_back(e);}/*List<T>& operator=(const List<T>& lt){if (this != <){clear();for (ayto e : lt)push_back(e);}return *this;}*/List<T>& operator=(List<T> lt){swap(_head, lt._head);//原来的空间给这个临时变量,因为这个临时变量是自定义类型,出了作用域后会自动调用析构函数return *this;}void clear()//clear是清除除了头节点意外的所以结点{iterator it = begin();while (it != end()){erase(it++);}}void push_back(const T& x)//一定要用引用,因为T不一定是内置类型{Node* tail = _head->_prev;Node* newnode = new Node(x);tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;/*insert(end(),x);*/}void pop_back(){Node* tail = _head->_prev;Node* prev = tail->_prev;delete tail;_head->_prev = prev;prev->_next = _head;//erase(--end());}void push_front(const T& x){Node* first = _head->_next;Node* newnode = new Node(x);_head->_next = newnode;newnode->_prev = _head;newnode->_next = first;first->_prev = newnode;//insert(begin(), x);}void pop_front(){Node* first = _head->_next;Node* second = first->_next;delete first;_head->_next = second;second->_prev = _head;//erase(begin());}void insert(iterator pos, const T& x){Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;}void 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;cur = nullptr;}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);}
private:Node* _head;
};
相关文章:
【C++】STL-list模拟实现
目录 1、本次需要实现的3个类即接口总览 2、list的模拟实现 2.1 链表结点的设置以及初始化 2.2 链表的迭代器 2.3 容量接口及默认成员函数 1、本次需要实现的3个类即接口总览 #pragma once #include<iostream> #include<assert.h> using namespace std; templ…...
Java 7大排序
🐵本篇文章将对数据结构中7大排序的知识进行讲解 一、插入排序 有一组待排序的数据array,以升序为例,从第二个数据开始(用tmp表示)依次遍历整组数据,每遍历到一个数据都再从tmp的前一个数据开始࿰…...
vue3 - 图灵
目录 vue3简介整体上认识vue3项目创建Vue3工程使用官方脚手架创建Vue工程[推荐] 主要⼯程结构 数据双向绑定vue2语法的双向绑定简单表单双向绑定复杂表单双向绑定 CompositionAPI替代OptionsAPICompositionAPI简单不带双向绑定写法CompositionAPI简单带双向绑定写法setup简写⽅…...
java设计模式八 享元
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度的对象。这种模式通过存储对象的外部状态在外部,而将不经常变化的内部状态(称为享元)存储在内部,以此来减…...
ELK原理详解
ELK原理详解 一、引言 在当今日益增长的数据量和复杂的系统环境中,日志数据的收集、存储、分析和可视化成为了企业运营和决策不可或缺的一部分。ELK(Elasticsearch、Logstash、Kibana)堆栈凭借其高效的性能、灵活的扩展性和强大的功能&…...
多线程学习Day09
10.Tomcat线程池 LimitLatch 用来限流,可以控制最大连接个数,类似 J.U.C 中的 Semaphore 后面再讲 Acceptor 只负责【接收新的 socket 连接】 Poller 只负责监听 socket channel 是否有【可读的 I/O 事件】 一旦可读,封装一个任务对象&#x…...
第33次CSP认证Q1:词频统计
🍄题目描述 在学习了文本处理后,小 P 对英语书中的 𝑛n 篇文章进行了初步整理。 具体来说,小 P 将所有的英文单词都转化为了整数编号。假设这 𝑛n 篇文章中共出现了 𝑚m 个不同的单词,则把它们…...
pytorch加载模型出现错误
大概的错误长下面这样: 问题出现的原因: 很明显,我就是犯了第一种错误。 网上的修改方法: 我觉得按道理哈,确实,蓝色部分应该是可以把问题解决了的。但是我没有解决,因为我犯了另外一个错…...
如何在Mac上恢复格式化硬盘的数据?
“嗨,我格式化了我的一个Mac硬盘,而没有使用Time Machine备份数据。这个硬盘被未知病毒感染了,所以我把它格式化为出厂设置。但是,我忘了备份我的文件。现在,我想恢复格式化的硬盘驱动器并恢复我的文档,您能…...
华为OD机试 - 手机App防沉迷系统(Java 2024 C卷 100分)
华为OD机试 2024C卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷B卷C卷)》。 刷的越多,抽中的概率越大,每一题都有详细的答题思路、详细的代码注释、样例测试…...
搜维尔科技:光学动作捕捉系统用于城市公共安全智慧感知实验室
用户名称:西安科技大学 主要产品:Optitrack Priime41 光学动作捕捉系统(8头) 在6米8米的空间内,通过8个Optitrack Priime41光学动作捕捉镜头,对人体动作进行捕捉,得到用户想要的人体三维空间坐…...
保研面试408复习 4——操作系统、计网
文章目录 1、操作系统一、文件系统中文件是如何组织的?二、文件的整体概述三、UNIX外存空闲空间管理 2、计算机网络一、CSMA/CD 协议(数据链路层协议)二、以太网MAC帧MTU 标记文字记忆,加粗文字注意,普通文字理解。 1、…...
实战攻防中关于文档的妙用
一、PPT钓鱼 简单制作一个用于钓鱼的PPTX文件 一般那种小白不知道PPT也能拿来钓鱼,这里主要是借用PPT中的”动作按钮”, 我们在插入的地方,选择“动作按钮” 然后在弹出的窗口处: 比如填入上线CS的语句:powershell.exe -nop -w …...
【使用ChatGPT的API之前】OpenAI API提供的可用模型
文章目录 一. ChatGPT基本概念二. OpenAI API提供的可用模型1. InstructGPT2. ChatGPT3. GPT-4 三. 在OpenAI Playground中使用GPT模型-ing 在使用GPT-4和ChatGPT的API集成到Python应用程序之前,我们先了解ChatGPT的基本概念,与OpenAI API提供的可用模型…...
【C语言】模拟实现深入了解:字符串函数
🔥引言 本篇将模拟实现字符串函数,通过底层了解更多相关细节 🌈个人主页:是店小二呀 🌈C语言笔记专栏:C语言笔记 🌈C笔记专栏: C笔记 🌈喜欢的诗句:无人扶我青云志 我自…...
钩子函数onMounted定义了太多访问MySQL的操作 导致数据库异常
先放几种后端遇到的异常,多数和数据库有关 pymysql.err.InternalError: Packet sequence number wrong - got 102 expected 1 127.0.0.1 - - [09/May/2024 17:49:37] "GET /monitorLastTenList HTTP/1.1" 500 AttributeError: NoneType object has no at…...
Excel文件解析---超大Excel文件读写
1.使用POI写入 当我们想在Excel文件中写入100w条数据时,使用XSSFWorkbook进行写入时会发现,只有将100w条数据全部加载到内存后才会用write()方法统一写入,效率很低,所以我们引入了SXXFWorkbook进行超大Excel文件读写。 通过设置 …...
TypeScript基础:类型系统介绍
TypeScript基础:类型系统介绍 引言 TypeScript,作为JavaScript的一个超集,引入了类型系统,这为开发大型应用程序带来了诸多好处。本文将介绍TypeScript类型系统的基础知识,帮助初学者理解其概念和用法。 基础知识 …...
【Unity】Unity项目转抖音小游戏(一) 项目转换
UnityWEBGL转抖音小游戏流程 业务需求,开始接触一下抖音小游戏相关的内容,开发过程中记录一下流程。 相关参考: 抖音文档:https://developer.open-douyin.com/docs/resource/zh-CN/mini-game/develop/guide/game-engine/rd-to-SC…...
element-ui 中修改loading加载样式
element-ui 中的 loading 加载功能,默认是全屏加载效果 设置局部,需要自定义样式或者修改样式,方法如下: import { Loading } from element-uiVue.prototype.$baseLoading (text) > {let loadingloading Loading.service({…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
【Pandas】pandas DataFrame dropna
Pandas2.2 DataFrame Missing data handling 方法描述DataFrame.fillna([value, method, axis, …])用于填充 DataFrame 中的缺失值(NaN)DataFrame.backfill(*[, axis, inplace, …])用于**使用后向填充(即“下一个有效观测值”)…...
