【C++】STL中List的基本功能的模拟实现
前言:在前面学习了STL中list的使用方法,现在我们就进一步的讲解List的一些基本功能的模拟实现,这一讲博主认为是最近比较难的一个地方,各位一起加油。
💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
目录标题
- List的模拟实现
- List三个基本类
- 结点类接口的实现
- list的正向迭代器类的实现
- List正向迭代器的接口实现
- 构造函数
- operator*运算符重载
- operator->运算符重载
- operator前置++和--与后置++和--
- operator==与operator!=
- List类的接口的实现
- 构造函数
- begin()和end()
- 尾插函数- push_back(const T& x)
- insert(iterator pos, const T& val)插入(pos之前的位置)
- push_front(const T& x)头插
- iterator erase(iterator pos)删除pos位置的值
- 尾删与头删
- clear()清空list
- size_t size() 查看链表元素
- bool empty()查看链表元素是否为空
- 拷贝构造函数
- 析构函数
- swap函数 交换链表中的元素
- operator=运算符重载
- 整体代码
List的模拟实现
List三个基本类
前面我们提到过,list本质上就是一个带头双向循环链表,这里我们要实现list的功能就要实现三个类:
- 模拟实现结点类
- 模拟实现迭代器的类
- 模拟list主要功能的类
结点类接口的实现
这里如果对带头双向链表不太熟悉的小伙伴可以去看看博主之前的文章带头双向循环链表
template <class T>
struct ListNode//链表的主体
{ListNode<T>* _prev;//C++中可不写struct,直接用名定义ListNode<T>* _next;T _data;//存节点的值ListNode(const T& x = T())//这个地方在讲模拟实现vector的时候也讲了,需要查看的可以看看之前的博客:_next(nullptr), _prev(nullptr), _data(x){}
};
看到这里很多小伙伴会有疑问为什么这里写的是ListNode*
- 在C++中是可以省略struct不写的,也就是说原本的样子应该是 struct ListNode * _prev
- 结构体模板或类模板在定义时可以不加 T,但 使用时必须加T
list的正向迭代器类的实现
template<class T, class Ref, class Ptr>
struct ListIterator//迭代器
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;//T表示基本类型, Ref表示引用返回,Ptr指代指针返回Node* _node;//记录链表ListIterator(Node* node)//传过来的位置就是迭代器从哪个位置开始
}
这里大部分人会有因为为什么这里迭代器的模板会有三个参数?因为如果我们只是使用普通迭代器的话确实一个参数就够了,但是有的情况我们是需要使用const迭代器的,难道我们还要在写一个类来专门放 const类型的迭代器嘛?
而后文list类的模拟实现中,我对迭代器进行了两种typedef:
普通迭代器:typedef ListIterator<T, T&, T*> iterator;
const迭代器:typedef ListIterator<T, const T&, const T*> const_iterator;
List正向迭代器的接口实现
构造函数
这里我们通过传过来的结点完成构造,让迭代器指向传过来结点的位置即可
ListIterator(Node* node)//传过来的位置就是迭代器从哪个位置开始:_node(node)
{
}
operator*运算符重载
前面我们说到过,Ref本质就是引用返回,无非就是const还是非const的类型的区分
Ref operator*()
{return _node->_data;
}
operator->运算符重载
至于为什么要写->的运算符重载,就是我们在list的使用的过程中传过去的不一定就是内置类型,还有可能是自定义类型(如下所示)
struct A
{int _a1;int _a2;A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}
};
void test_list2()
{list<A> lt;A aa1(1, 1);A aa2 = { 1, 1 };lt.push_back(aa1);lt.push_back(aa2);lt.push_back(A(2, 2));lt.push_back({ 3, 3 });lt.push_back({ 4, 4 });list<A>::iterator it = lt.begin();while (it != lt.end()){cout << it->_a1 << ":" << it->_a2 << endl;//本质上编译器会省略一个->,所以实际上写的是it->_A->_a1cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;++it;}cout << endl;
}
Ptr operator->()//本质上就是重载自定义类型,帮你找到内置类型,然后再找到内置类型的数据
{return &_node->_data;
}
operator前置++和–与后置++和–
这里我们提一下,对于前置++和后置++还有–等,我们主要传一个int类型的数据来进行区分
Self& operator++()//前置++
{_node = _node->_next;return *this;
}Self operator++(int)//后置++,加上int以便区分
{Self 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;
}
operator==与operator!=
代码思路:对于如何判断两个迭代器是否相等,我们只需要判断两个迭代器所指向的位置是否相等即可。
bool operator != (const Self& it)
{return _node != it._node;
}bool operator == (const Self& it)
{return _node == it._node;
}
List类的接口的实现
代码思路:这里我们主要是通过两个迭代器帮助我们去遍历list,然后一个const迭代器是只读的作用,一个非const迭代器是即可读又可写的作用
template <class T>
class list//链表
{typedef ListNode<T> Node;
public:typedef ListIterator<T, T&, T*> iterator;//正向迭代器typedef ListIterator<T, const T&, const T*> const_iterator;//const迭代器
private:Node* _head;size_t _size;//记录链表元素个数
};
构造函数
这里我们就采用双向带头链表的思路,初始化的时候让其的前驱指针和next指向他的哨兵位即可。
void empty_init()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
list()//默认构造
{empty_init();
}
begin()和end()
iterator begin()//begin应该是哨兵位的下一个结点
{return _head->_next;
}
iterator end()//因为是带头双向链表,所以通常没有尾部的这个说法,一般结束的时候就是在哨兵位这个结点就是尾结点
{return _head;
}const_iterator begin()const//只读的版本
{return _head->_next;
}const_iterator end() const
{return _head;
}
尾插函数- push_back(const T& x)
关于尾插这部分的内容,我们在之前数据结构那部分讲的挺详细的不懂的话可以看看博主之前的博客。
void push_back(const T& x)//尾插
{//insert(end(), x);Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点tail->_next = newnode;newnode->_prev = tail;//使newnode和头结点_head构成循环newnode->_next = _head;
}
insert(iterator pos, const T& val)插入(pos之前的位置)
这里我们会发现使用insert会改变了底层,会导致迭代器失效,所以使用的时候要及时更新迭代器。
void insert(iterator pos, const T& val)//插入
{Node* cur = pos._node;//找到当前结点的链表Node* newnode = new Node(val);Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;
}
push_front(const T& x)头插
这里我们可以顺带把尾插也给优化一下
void push_front(const T& x)//头插
{insert(begin(), x);
}
void push_back(const T& x)//尾插
{insert(end(), x);
}
iterator erase(iterator pos)删除pos位置的值
这里我们也需要注意的是,删除和插入数据都会导致迭代器失效,因此我们需要及时的更新迭代器
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;_size--;return iterator(next);
}
尾删与头删
void pop_back()//尾删
{erase(end() - 1);
}void pop_front()
{erase(begin());
}
clear()清空list
void clear()
{iterator it = begin();//通过迭代器依次遍历清除while (it != end()){it = erase(it);}
}
size_t size() 查看链表元素
size_t size() const
{return _size;
}
bool empty()查看链表元素是否为空
bool empty()
{return _size == 0;
}
拷贝构造函数
代码思路:我们只需要对链表的元素依次尾插到新的链表中即可
list(const list<T>& lt)
{empty_init();for (auto& e : lt){push_back(e);}
}
析构函数
~list()//析构
{clear();delete _head;_head = nullptr;
}
swap函数 交换链表中的元素
void swap(list<T>& it)//it要被修改
{std::swap(_head, it._head);std::swap(_size, it._size);
}
operator=运算符重载
list<T>& operator=(list<T> it)
{swap(*this,it);return *this;
}
整体代码
#include<iostream>
#include <assert.h>
using namespace std;namespace bit
{template <class T>struct ListNode//链表的主体{ListNode* _prev;//C++中可不写struct,直接用名定义ListNode* _next;T _data;ListNode(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};template<class T, class Ref, class Ptr>struct ListIterator//迭代器{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;//T表示基本类型, Ref表示引用返回,Ptr指代指针返回Node* _node;//记录链表ListIterator(Node* node)//传过来的位置就是迭代器从哪个位置开始:_node(node){}Ref operator*(){return _node->_data;}// list<int>::ListIterator it; it->data;//list<Data>::ListIterator it; it->Data->data;Ptr operator->()//本质上就是重载自定义类型,帮你找到内置类型,然后再找到内置类型的数据{return &_node->_data;}Self& operator++()//前置++{_node = _node->_next;return *this;}Self operator++(int)//后置++{Self 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;}bool operator != (const Self& it){return _node != it._node;}bool operator == (const Self& it){return _node == it._node;}};template <class T>class list//链表{typedef ListNode<T> Node;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin()const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list()//默认构造{empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}~list()//析构{clear();delete _head;_head = nullptr;}void swap(list<T>& it)//it要被修改{std::swap(_head, it._head);std::swap(_size, it._size);}list<T>& operator=(list<T> it){swap(*this,it);return *this;}void push_back(const T& x)//尾插{//insert(end(), x);Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点tail->_next = newnode;newnode->_prev = tail;//使newnode和头结点_head构成循环newnode->_next = _head;}void push_front(const T& x)//头插{insert(begin(), x);}void insert(iterator pos, const T& val)//插入{Node* cur = pos._node;//找到当前结点的链表Node* newnode = new Node(val);Node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;}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;_size--;return iterator(next);}void pop_back()//尾删{erase(end() - 1);}void pop_front(){erase(begin());}size_t size() const{return _size;}bool empty(){return _size == 0;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}private:Node* _head;size_t _size;};
好啦,今天的内容就到这里啦,下期内容预告stl中stack和queue的使用与模拟实现.
结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。
相关文章:
【C++】STL中List的基本功能的模拟实现
前言:在前面学习了STL中list的使用方法,现在我们就进一步的讲解List的一些基本功能的模拟实现,这一讲博主认为是最近比较难的一个地方,各位一起加油。 💖 博主CSDN主页:卫卫卫的个人主页 💞 👉 …...
C语言基础——函数
ʕ • ᴥ • ʔ づ♡ど 🎉 欢迎点赞支持🎉 个人主页:励志不掉头发的内向程序员; 专栏主页:C语言基础; 文章目录 前言 一、函数的概念 二、库函数 2.1 库函数和头文件 2.2 库函数的使用/…...
《精通ChatGPT:从入门到大师的Prompt指南》第1章:认识ChatGPT
第1章:认识ChatGPT 1.1 ChatGPT是什么 ChatGPT,全称为Chat Generative Pre-trained Transformer,是由OpenAI开发的一种先进的自然语言处理模型。它利用了深度学习中的一种技术——Transformer架构,来生成类人文本。ChatGPT通过对…...
智慧视觉怎么识别视频?智慧机器视觉是通过什么步骤识别视频的?
智慧视觉功能怎么识别视频?智慧视觉是搭载在智能设备比如手机、AI盒子、机器视觉系统上的一个应用程序或特性,采用计算机视觉和人工智能的技术来识别图像或视频中的内容。如果想了解视频识别,就要明白智慧视觉功能会涉及的以下几个关键步骤和…...
NineData蔡冬者参与编写墨天轮《2023年中国数据库行业年度分析报告》正式发布!
为明晰发展脉络,把握未来趋势,墨天轮于5月29日正式发布 《2023年中国数据库年度行业分析报告》。该报告由墨天轮联合业界专家学者共同编写,共330页,旨在梳理和洞察中国数据库行业的发展趋势、技术创新、市场动态以及面临的挑战&am…...
帝国cms接入腾讯云人脸识别认证代码
利用帝国cms在做一些会员系统的时候,需要做人脸识别认证,之前接入了某api接口,发现身份证识别率真的低,还好充值的少,否则要出问题,后来发现会员注册率降低了不少,最终还是决定使用腾讯云的人脸…...
计算机网络-OSI七层参考模型与数据封装
目录 一、网络 1、网络的定义 2、网络的分类 3、网络的作用 4、网络的数据传输方式 5、网络的数据通讯方式 二、OSI七层参考模型 1、网络参考模型定义 2、分层的意义 3、分层与功能 4、TCP\IP五层模型 三、参考模型的协议 1、物理层 2、数据链路层 3、网络层 4…...
[职场] 为什么不能加薪? #学习方法#知识分享#微信
为什么不能加薪? 不能加薪的根本原因,终于被我找到了! 朋友们!职场这个地方是个很神奇的世界,有些规则并不是你想象的那样。我们都希望能在这个世界里施展自己的才华,获得升职加薪的荣耀。然而,…...
[matlab]折线图之多条折线如何绘制实心圆作为标记点
使用MarkerFaceColor是标记点填充的颜色,b,表示blue,蓝色 plot(x, a, d--, MarkerFaceColor, b); % 绘制仿真结果的曲线如果一张图多条曲线那么每条曲线需要单独调用一次plot,每个plot间用hold on 连接 plot(x, a, d--, MarkerF…...
HTML:认识HTML与基本语法的学习
前言 HTML(超文本标记语言)是用于创建网页的标记语言,由一系列标签组成,定义网页中的元素。由蒂姆伯纳斯 - 李于1990年代初发明,最初用于科研机构间共享文档,迅速演变为Web开发基础。无论是电商、博客、新…...
如何掌握 Java 正则表达式 的基本语法及在 Java 中的应用
正则表达式是一种用于匹配字符串的模式,在许多编程语言中广泛使用。Java 正则表达式提供了强大的文本处理能力,能够对字符串进行查找、替换、分割等操作。 一、正则表达式的基本语法 正则表达式由普通字符和特殊字符组成。普通字符包括字母、数字和标点…...
深度学习(三)
5.Functional API 搭建神经网络模型 5.1利用Functional API编写宽深神经网络模型进行手写数字识别 import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom sklearn.datasets import load_irisfrom sklearn.model_selection import train_test_splitfrom…...
文件系统小册(FusePosixK8s csi)【2 Posix标准】
文件系统小册(Fuse&Posix&K8s csi)【2 Posix】 往期文章:文件系统小册(Fuse&Posix&K8s csi)【1 Fuse】 POSIX:可移植操作系统接口(标准) 1 概念 POSIX:…...
vue 弹出框组件重复打开时,资源重新加载
新增或者编辑内容使用同一个弹出框,如何使数据可以重新加载? 1、绑定时间戳,有副作用,屏幕会闪烁一下 <el-dialog :key"timer" > </el-dialog> 2、v-if和:visible.sync同时使用 <el-dialogv-if"…...
图像的IO操作
代码: import cv2 as cvimport matplotlib.pyplot as plt#读取图像img cv.imread("../data/images/zidane.jpg")#显示图像#2.1 OpenCVcv.imshow("dili",img)cv.waitKey(0)cv.destroyAllWindows()#2.2 matplotlibplt.imshow(img[:,:,::-…...
关于 Vue.js 中`transition`组件使用:页面切换动画和标签移动动画都是要用到的
一、引言 在 Vue.js 中,transition组件提供了一种简单而强大的方式来实现页面过渡效果。它可以让元素在状态改变时,如进入或离开视图时,以平滑的动画方式进行过渡。通过transition,我们可以为应用增添更加生动和吸引人的用户体验…...
Flink Rest Basic Auth - 安全认证
背景 公司目前需要将Flink实时作业云化,构建多租户实时计算平台。目前考虑为了资源高效利用,并不打算为每个租户部署一套独立的Kubernetes集群。也就意味着多个租户的作业可能会运行在同一套kubernets集群中。此时实时作业的任务就变的很危险,因为网络可能是通的,就会存在…...
安全U盘和普通U盘有什么区别?
安全U盘(也称为加密U盘或安全闪存驱动器)与普通U盘肯定是有一些区别的,从字面意思上来看,就能看出,安全U盘是能够保护文件数据安全性的,普通U盘没这一些功能的,可随意拷贝文件,不防盗…...
大数据与数据科学的学科边界
大数据和数据科学是两个紧密相关但又不完全相同的学科。它们都关注数据的收集、管理、分析和解释,但侧重点有所不同。 大数据主要关注处理和分析大规模数据集的技术和方法。它涉及到数据存储、数据处理、数据挖掘、数据可视化和分布式计算等方面的技术。大数据的目…...
Chrome 源码阅读:跟踪一个鼠标事件的流程
我们通过在关键节点打断点的方式,去分析一个鼠标事件的流程。 我们知道chromium是多进程模型,那么,我们可以推测:一个鼠标消息先从主进程产生,再通过跨进程通信发送给渲染进程,渲染进程再发送给WebFrame&a…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

