C++STL详解(五)——list类的具体实现
一.本次所需实现的三个类及其成员函数接口
链表首先要有结点,因此我们需要实现一个结点类。
链表要有管理结点的结构,因此我们要有list类来管理结点。
链表中还要有迭代器,而迭代器的底层其实是指针。但是我们现有的结点类无法完成迭代器的行为,因此我们还需要实现一个迭代器类。
因此,我们要实现的三个类分别是:结点类、迭代器类、链表类。
namespace trousers
{template<class T>struct _list_node{//初始化_list_node();//变量T data;//数值域_list_node<T>* prev;//前驱指针_list_node<T>* next;//后继指针};//迭代器 //由于list_node无法遍历和支持迭代器,因此我们需要手动实现一个迭代器版本//Ref和Ptr分别代表引用和指针类型,我们可以用一个类模板重载出两个迭代器版本。(迭代器里面需要用到指针和引用),一个模板的话不够用template<class T, class Ref, class Ptr>struct _list_iterator{typedef _list_node<T> node;typedef _list_iterator<T,Ref,Ptr> self;self operator++();self operator++(int);self operator--();self operator--(int);Ref operator*();Ptr operator->();//_pnode->_val operator->(*this)->_valself operator==(const self& t)constself operator!=(const self& t)const//变量node* _pnode;};//链表template <class T>class list{public:typedef _list_node<T> node;typedef _list_iterator<T,T&,T*> iterator;typedef _list_iterator<T, const T&, const T*> const_iterator;//默认成员函数list();list(const list<T>& lt);list<T>& operator=(const list<T>& lt);~list();//迭代器相关函数iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//访问容器相关函数T& front();T& back();const T& front() const;const T& back() const;//插入、删除函数void insert(iterator pos, const T& x);iterator erase(iterator pos);void push_back(const T& x);void pop_back();void push_front(const T& x);void pop_front();//其他函数size_t size() const;void resize(size_t n, const T& val = T());void clear();bool empty() const;void swap(list<T>& lt);//私有变量private:node* _head;//指向哨兵位};
}
二.结点类的模拟实现
list的底层其实是一个带头双向循环链表。
因此,我们需要实现的结点类中需要的成员为:数据、前一个结点的指针、后一个结点的指针。
对于该类而言,我们不需要在类中完成任何行为,因此我们仅仅只需要实现一个构造函数即可。而该类由于都是内置成员,因此我们的析构函数可以由编译器生成。
2.1结点类的构造函数
结点类的构造函数实现起来是比较简单的,我们仅仅需要将val置想要的值,并将两个指针置空即可。
_list_node(const T& data=T()):_data(),_prev(nullptr),_next(nullptr){}
三.迭代器类的模拟实现
3.1迭代器的设计思路
在实现了结点类之后,我们就要开始实现迭代器类了。
由于我们无法通过只有一个参数的类模板实现const和非const的两个迭代器
因此我们这里的类模板有三个参数。
template<class T, class Ref, class Ptr>
其中的Ref表示引用,Ptr表示解引用。
3.2迭代器类存在的意义
在之前实现string和vector的时候,我们都不需要实现一个迭代器类,为什么实现list的时候就需要实现一个迭代器类了呢?
这是因为,string和vector对象都将数据存储在了一块连续的内存空间,我们通过指针操纵空间从而完成自增、自减、解引用等操作,然后就可以对数据进行一系列的操作,因此string和vector当中的迭代器就是原生的指针。
但是,对于list而言,各个结点在内存中的分布并不连续,因此我们不能通过对结点的自增自减等操作来完成迭代器的行为。
而迭代存在的意义就是,让使用者不必关心底层的实现,可以用简单统一的方式对容器内的数据进行访问。
既然list的结点指针的行为不满足迭代器的定义,那么我们就需要对结点指针进行封装,对结点指针的各个运算符进行重载,使得其支持像vector、string中的迭代器一样的操作。
举个例子,我们在用list的自增行为时,实际上是执行了p=p->next语句。
总结:list的迭代器类,实际上只是对结点的指针进行了封装,并对其各个操作符进行了重载,使得结点指针的各种行为看起来和普通指针一样。
3.3构造函数
迭代器类的构造函数实际上是对结点指针进行了封装而已,其成员变量只有一个结点的指针,因此我们的构造函数直接根据所给的结点指针构造出一个迭代器对象即可。
_list_iterator(node* pnode):_pnode(pnode){}
3.4++运算符的重载
3.4.1前置++操作符
对于前置++操作符,我们的实现思路非常简单,直接将结点指针指向next,然后返回自增后的结果即可。
self operator++(){_pnode = _pnode->_next;return *this;}
3.4.2后置++操作符
对于后置++操作符,我们采取使用一个临时变量记录该结点的方式,对结点指针完成自增,并返回临时变量即可。
self operator++(int){self tmp(*this);_pnode = _pnode->_next;return tmp;}
3.5--运算符的重载
--操作符的逻辑和++操作符的逻辑基本上是一样的,只不过是将自身修改为prev而不是next。
self operator--(){_pnode = _pnode->_prev;return *this;}self operator--(int){self tmp(*this);_pnode = _pnode->_prev;return tmp;}
3.6==运算符的重载
在使用迭代器遍历时,难免会比较两个迭代器是否相同,因此我们还需要实现==操作符。
而两个迭代器是否相同,实际上就是判断这两个迭代器是不是同一个位置上的迭代器,因此,我们只需要比较这两个迭代器的地址即可。
bool operator==(const self& t)const{return _pnode == t._pnode;}
3.7!=运算符的重载
!=操作符和==操作符的作用相反,我们要判断的是这两个迭代器的地址是不是不同。
bool operator!=(const self& t)const{return _pnode != t._pnode;}
3.8*运算符的重载
当我们使用*操作符时,其实就是想要得到这个地址的数据,因此我们直接返回当前指针数据所指向的数据即可,但由于我们可能会通过解引用修改数据,因此我们这里可以返回引用。
Ref operator*(){return _pnode->_data;}
3.9->运算符的重载
在一些场景下,我们可能还会使用到->操作符。
譬如如下场景:
当list容器内的每个结点存储的是自定义类型时,那么当我们拿到一个位置的迭代器,我们可能还会通过->操作符来访问该类型内部的成员。
如下例:
list<vector> d;
vector<int> v1 = { 1,2,3 };
vector<int> v2 = { 4,5,6 };
vector<int> v3 (3,5);
d.push_back(v1);
d.push_back(v2);
d.push_back(v3);
list <vector>::iterator pos = it.begin();
cout << pos->size() << endl;
因此,有些情况下我们会使用到->操作符。
对于->操作符的重载,我们直接返回结点当中所存储数据的地址即可。
Ptr operator->()//_pnode->_val operator->(this)->_val{//(*this)->_data;return &(_node->_data);}
说到这里,你可能会觉得有些不对,按照这种重载方式的话,我们似乎需要两个->才能调到我们想要的数据,也就是这样:
因为()内部的this是被省略的,因此我们实际上写应该是这样的
但是一个地方出现两个箭头的可读性有些过于差劲了,因此编译器在这里做了一些特殊的处理,省略了一个箭头,也就是我们所写的版本。
四.list的模拟实现
4.1默认成员函数
4.1.1构造函数
list是一个带头双向循环链表,因此在构造一个list对象时,直接申请一个头节点并让前驱指针和后继指针都指向自己即可。
list(){_head = new node;_head->_next = _head;_head->_prev = _head;}
4.2.2拷贝构造函数
拷贝构造函数,我们需要先申请一个结点,然后申请一个头结点,之后我们再将原list链表一个一个通过尾插拷贝过去即可。
list(const list<T>& lt){_head = new node;_head->_next = _head;_head->_prev = _head;for (const auto& e : lt){push_back(e);}}
4.2.3赋值运算符重载函数
赋值运算符重载和拷贝构造的实现方式是类似的,我们可以先将被赋值的链表清空然后一个一个通过尾插拷贝过去。
list<T>& operator=(const list<T>& lt){if (this != <)//防止自己给自己赋值,以避免性能浪费{clear();for (const auto& e : lt){push_back(e);}}return *this;}
但是这种写法过于繁琐,我们也可以换一种思路,我们不采取引用传参,那么我们将会传入一个形参,之后我们和形参进行交换即可完成任务,而当我们的函数运行结束后还会自动销毁掉形参。
list<T>& operator=(const list<T> lt)//编译器接受右值时自动调用其拷贝构造函数,构造出形参。{swap(lt);return *this;}
4.2.4析构函数
对于析构函数,我们首先使用clear清理一下容器内的数据,然后将头结点释放,之后再将指针置空即可。
~list(){clear();delete _head;_head = nullptr;}
五.迭代器相关函数
begin和end
begin是返回第一个有效数据的迭代器,因此我们要返回头节点的下一个结点
iterator begin(){return iterator(_head->_next);}
这里需要大家注意的是,我们要返回迭代器而不是结点指针,因此我们需要用迭代器的构造函数构造出指向同一块空间的迭代器类型的匿名变量用于返回。
end是返回最后一个有效数据的后一个数据的迭代器,在双向循环链表中要返回的是头结点
iterator end(){return iterator(_head);}
当然,除了这两个之外,我们还需要实现两个const版本的函数
const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}
六.访问容器相关函数
6.1front和back
front返回第一个有效数据,back返回最后一个有效数据,因此我们在实现front和back函数时,直接返回第一个有效数据的引用和最后一个有效数据的引用即可。
T& front(){return *begin();}T& back(){return *(--end());}
除此之外,我们还需要重载一对用于const对象的front和back函数。
const T& front() const{return *begin();//我们已经重载了解引用}const T& back() const{return *(--end());}
6.2插入、删除函数
6.2.1insert
对于insert函数,我们是这样写的:
- 首先检查一下插入位置的合法性。
- 然后用要插入的数据新建一个结点。
- 然后记录下要插入位置处结点的指针
- 之后建立节点之间的双向关系
void insert(iterator pos, const T& x){assert(pos._pnode);node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;}
6.2.2erase
erase可以删除所给的迭代器位置的结点。
实现思路为:
- 先根据迭代器得到该位置处的结点cur
- 然后通过cur找到prev和next指针
- 之后删掉cur,并建立prev和next之间的双向关系。
- 返回next位置
iterator erase(iterator pos){assert(pos._pnode);assert(pos != end());node* cur = pos._pnode;node* prev = cur->_prev;node* next = cur->_next;delete cur;prev->_next = next;next->_prev = prev;return iterator(next);}
6.2.3对头尾的插入和删除函数
我们直接复用insert和erase即可。
void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}
七.其他函数
7.1size
对于size函数,我们可以通过迭代器的遍历获取个数。
size_t size() const{size_t sz = 0;const_iterator it = begin();while (it != end()){it++;sz++;}return sz;}
除了这个方法外,还有一个方法可以获取到个数。
我们可以多设置一个私有成员size,在插入逻辑中,每插入一个则size+1,在删除逻辑中,每删除一个,则size-1。这样也可以获取到size 的个数。
7.2resize
resize函数规则:
- 若当前容器的size小于所给n,则尾插结点,直到size等于n
- 若当前容器的size大于所给n,则只保留前n个有效数据。
那么,如何实现resize函数呢?
- 首先,我们定义一个len表示链表的长度
- 遍历到结尾后,比较len和n的大小
- 若len较大,则删除掉多余的结点
- 若len较小,则尾插数值为x的结点。
void resize(size_t n, const T& val = T()){iterator it = begin();size_t len = 0;while (it != end()){it++;len++;}while (len < n){push_back(val);len++;}while (len > n){pop_back();len--;}}
7.3clear
对于clear函数,我们需要做的是清空链表的有效数据。
因此我们逐个删除掉链表的结点,只保留头节点即可。
void clear(){iterator it=begin();while (it != begin()){it = erase(it);//防止迭代器失效}}
7.4empty
empty函数是判空的,我们有很多种方法判断链表是否为空。
这里我们通过判断该容器的begin函数和end函数所返回的迭代器是否相同来进行判断。(如果相同,则代表只有一个头结点)
bool empty() const{return begin() == end();}
7.5swap
swap函数用于交换两个容器,在list容器当中存储的实际上只有头结点的指针,因此我们只要交换一下头节点的指针即可。
void swap(list<T>& lt){::swap(_head, lt._head);}
注意点:在此处调用的swap是库中的swap,我们在swap前面加上域作用限定符,即可告诉编译器优先在全局范围内寻找swap函数。
相关文章:
C++STL详解(五)——list类的具体实现
一.本次所需实现的三个类及其成员函数接口 链表首先要有结点,因此我们需要实现一个结点类。 链表要有管理结点的结构,因此我们要有list类来管理结点。 链表中还要有迭代器,而迭代器的底层其实是指针。但是我们现有的结点类无法完成迭代器的…...
鸿蒙(API 12 Beta3版)【使用投播组件】案例应用
华为视频接入播控中心和投播能力概述** 华为视频在进入影片详情页播放时,支持在控制中心查看当前播放的视频信息,并进行快进、快退、拖动进度、播放暂停、下一集、调节音量等操作,方便用户通过控制中心来操作当前播放的视频。 当用户希望通…...
【STM32项目】在FreeRtos背景下的实战项目的实现过程(一)
个人主页~ 这篇文章是我亲身经历的,在做完一个项目之后总结的经验,虽然我没有将整个项目给放出来,因为这项目确实也是花了米让导师指导的,但是这个过程对于STM32的实战项目开发都是非常好用的,可以说按照这个过程&…...
C#垃圾处理机制相关笔记
C#编程中的垃圾处理机制主要通过垃圾回收器(Garbage Collector,GC)实现自动内存管理。C#作为一种托管语言,其垃圾处理机制显著减轻了程序员的内存管理负担,与C语言等非托管语言形成鲜明对比。具体介绍如下:…...
C语言memcmp函数
目录 开头1.什么是memcmp函数?2.memcmp函数的内部程序流程图 3.memcmp函数的实际应用比较整型数组比较短整型二维数组比较结构体变量…… 结尾 开头 大家好,我叫这是我58。今天,我们要学一下关于C语言里的memcmp函数的一些知识。 1.什么是memcmp函数?…...
低代码: 组件库测试之Vue环境下的测试工具以及测试环境搭建
Vue Test Utils Vue Test Utils 1 targets Vue 2. Vue Test Utils 2 targets Vue 3. 特别注意要使用 版本 2.0.0 以上 提供特定的方法,在隔离的话环境下,进行组件的挂载,以及一系列的测试 配置开发环境 手动配置, 是比较麻烦的vue cli 是基于插件架构的, 插件可以: 安装对…...
【Vue3】高颜值后台管理模板推荐
ELP - 权限管理系统 基于Vue 3框架与PrimeVue UI组件库技术精心构建的高颜值后台权限管理系统模板。该模板系统已成功实现基于RBAC(Role-Based Access Control)模型的权限管理系统和字典数据管理模块,后端则使用了Spring Boot框架࿰…...
详细介绍Pytorch中torchvision的相关使用
torchvision 是 PyTorch 的一个官方库,主要用于处理计算机视觉任务。提供了许多常用的数据集、模型架构、图像转换等功能,使得计算机视觉任务的开发变得更加高效和便捷。以下是对 torchvision 主要功能的详细介绍: 1. 数据集(Dat…...
AI部署——主流模型推理部署框架
我们以最经典的Yolov5目标检测网络为例解释一下10种主流推理部署框架的大概内容,省略模型训练的过程,只讨论模型转换、环境配置、推理部署等步骤。 Intel的OpenVINO — CPUNvidia的TensorRT — GPU/CPUOpenCV DNN Module — GPU/CPUMicrosoft ONNX Runti…...
PyTorch之loading fbgemm.dll异常的解决办法
前言 PyTorch是一个深度学习框架,当我们在本地调试大模型时,可能会选用并安装它,目前已更新至2.4版本。 一、安装必备 1. window 学习或开发阶段,我们通常在window环境下进行,因此需满足以下条件: Windo…...
Vscode——如何实现 Ctrl+鼠标左键 跳转函数内部的方法
一、对于Python代码 安装python插件即可实现 二、对于C/C代码 安装C/C插件即可实现...
力扣热题100_回溯_78_子集
文章目录 题目链接解题思路解题代码 题目链接 78. 子集 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入ÿ…...
浏览器如何工作(一)进程架构
分享cosine 大佬,版权©️大佬所有 浏览器的核心功能 浏览器,“浏览” 是这个产品的核心,浏览无非分为两步: 获取想浏览的资源 展示得到的资源 现代浏览器还增加了交互功能,这涉及到脚本运行。因此,…...
【LeetCode】两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。 你可以按任意顺序返回答案。 示例 1…...
UE5学习笔记11-为拿取武器添加动画
一、一点说明 动画实例通过扩展为所有机器上的每个字符都存在动画蓝图,动画实例只能访问该计算机上的变量。 二、思路 我在武器组件中有一个武器类的指针,判断当前指针是否为空去判断当前角色是否装备武器 三、实现 1.在角色C类中添加是否装备武器的函…...
68. 文本左右对齐【 力扣(LeetCode) 】
一、题目描述 给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单…...
【中等】 猿人学web第一届 第6题 js混淆-回溯
文章目录 请求流程请求参数 加密参数定位r() 方法z() 方法 加密参数还原JJENCOde js代码加密环境检测_n("jsencrypt")12345 计算全部中奖的总金额请求代码注意 请求流程 请求参数 打开 调试工具,查看数据接口 https://match.yuanrenxue.cn/api/match/6 请…...
低、中、高频率段具体在不同应用中的范围是多少
1、低频率段(Low Frequency Range) ①建筑声学和噪声控制:通常将20 Hz 到 200 Hz 的频率范围视为低频段。在这一范围内,声音的波长较长,通常与低音(如重低音音乐)和建筑结构中的振动有关。 ②…...
Oxford Model600 Model400低温氦压缩机cryogenic helium compressor手侧
Oxford Model600 Model400低温氦压缩机cryogenic helium compressor手侧...
Golang面试题四(并发编程)
目录 1.Go常见的并发模型 2.哪些方法安全读写共享变量 3.如何排查数据竞争问题 4.Go有哪些同步原语 1. Mutex (互斥锁) 2. RWMutex (读写互斥锁) 3. Atomic 3.1.使用场景 3.2.整型操作 3.3.指针操作 3.4.使用示例 4. Channel 使用场景 使用示例 5. sync.WaitGr…...
计算机学生高效记录并整理编程学习笔记的方法
哪些知识点需要做笔记? 以下是我认为计算机学生大学四年可以积累的笔记。 ① 编程语言类(C语言CJava):保留课堂笔记中可运行的代码部分,课后debug跑一跑。学习语言初期应该多写代码(从仿写到自己写&#…...
【书生大模型实战】L2-LMDeploy 量化部署实践闯关任务
一、关卡任务 基础任务(完成此任务即完成闯关) 使用结合W4A16量化与kv cache量化的internlm2_5-7b-chat模型封装本地API并与大模型进行一次对话,作业截图需包括显存占用情况与大模型回复,参考4.1 API开发(优秀学员必做)使用Func…...
《编程学习笔记之道:构建知识宝库的秘诀》
在编程的浩瀚世界里,我们如同勇敢的探险家,不断追寻着知识的宝藏。而高效的笔记记录和整理方法,就像是我们手中的指南针,指引着我们在这片知识海洋中前行,不至于迷失方向。在这篇文章中,我们将深入探讨如何…...
DETR论文,基于transformer的目标检测网络 DETR:End-to-End Object Detection with Transformers
transformer的基本结构: encoder-decoder的基本流程为: 1)对于输入,首先进行embedding操作,即将输入映射为向量的形式,包含两部分操作,第一部分是input embedding:例如,在NLP领域&…...
untiy有渲染线程和逻辑线程嘛
之前我也这么认为,其实unity引擎是单线程的,当然后续的jobs不在考虑范围内 如果你在一个awake 或者 start方法中 延时,是会卡住主线程的 比如 其实游戏引擎有一个基础简单理解,那就是不断的进行一个循环,在这个周期循…...
什么是数据仓库ODS层?为什么需要ODS层?
在大数据时代,数据仓库的重要性不言而喻。它不仅是企业数据存储与管理的核心,更是数据分析与决策支持的重要基础。而在数据仓库的各个层次中,ODS层(Operational Data Store,操作型数据存储)作为关键一环&am…...
permutation sequence(
60. Permutation Sequence class Solution:def getPermutation(self, n: int, k: int) -> str:def rec(k, l, ans, n):if(n0): return# 保留第一个位置,剩下数字的组合leftCom math.factorial(n - 1) #用于计算 (n-1) 的阶乘值ele k // leftCommod k % leftCo…...
PCL 三线性插值
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 三线性插值是一种在三维空间中使用已知数据点进行插值的方法。它是在立方体内的插值方法,通过利用立方体的八个顶点的已知值来估算立方体内任意一点的值。三线性插值扩展了一维的线性插值和二维的双线性插值。其基…...
JVM虚拟机(一)介绍、JVM内存模型、JAVA内存模型,堆区、虚拟机栈、本地方法栈、方法区、常量池
目录 学习JVM有什么用、为什么要学JVM? JVM是什么呢? 优点一:一次编写,到处运行。(Write Once, Run Anywhere,WORA) 优点二:自动内存管理,垃圾回收机制。 优点三&am…...
Python利用xlrd复制一个Excel中的sheet保留原格式创建一个副本(注:xlrd只能读取xls)
目录 专栏导读库的介绍库的安装完整代码总结 专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文…...
如何做视频网站技术指标/外链价格
随着区块链技术从概念到落地的发展趋势,从2017年开始区块链技术已经有了初步的与现实商业场景结合的可能,这个热度在2018年升到顶端,区块链技术在全球开始部署应用。而2018年的中国,舆论和资本在区块链领域双管齐下,各…...
政府网站建设费用明细/百度客服中心人工电话
Charles 的简介如何安装 Charles将 Charles 设置成系统代理Charles 主界面介绍过滤网络请求截取 iPhone 上的网络封包截取 Https 通讯信息模拟慢速网络修改网络请求内容给服务器做压力测试修改服务器返回内容总结 简介 Charles 是在 Mac 下常用的网络封包截取工具,在…...
企业网站风格/微信推广软件
2019独角兽企业重金招聘Python工程师标准>>> How to implement the function that supports concurrent downloading? 在看完 小心,AsyncTask 不是萬能的 以及 深入研究 IntentService 原始碼 這兩篇文章後,我想你應該已經對如何寫個正確處理…...
wordpress菜单栏插件/千度搜索引擎
前言 为了能够真实模拟实验室的Autolabor_pro1小车,我就自己用solidworks2018基本仿画出Autolabor模型,当然我知道官网有urdf文件但是好像官方的只有一个车子的base部分没有其他任何对外的传感器,因此我就索性自己利用solidworks构建自己的机…...
phpcmsv9网站地图/怎样开自己的网站
我们在使用笔记本电脑连接网络的时候,经常会碰到感叹号,这个感叹号代表了无internet访问,有很多用户都不知道这个问题怎么解决,那么win7系统无internet访问怎么办呢?今天为大家分享win7系统无internet访问的解决方法。无internet…...
wordpress模板页面说明/百度平台电话多少
我写一个系列,专门记一记长见识的代码 深挖了求边缘的程序,发现matlab还有这种函数?或者说用法? 解析: >> A[1 2 8;4 7 6;2 6 7;5 6 1]; max(A)ans 5 7 8>> A[1 2 8;4 7 6;2 6 7;5 6 1]; ma…...