C++第十二弹 -- STL之list模拟实现
文章索引
- 前言
- 模拟实现list
- 1. ListNode节点类
- 2. list的迭代器封装
- 3. 反向迭代器
- 4. list类的模拟实现
- 测试代码
- list的反向迭代器
- 总结
前言
通过模拟实现可以让我们更加深刻的理解C++底层STL的实现逻辑, 本篇就对list的底层进行模拟实现.
博客主页: 酷酷学!!! 点击关注 共同进步!!!
正文开始
模拟实现list
要模拟实现list, 必须要熟悉list的底层结构以及其接口的含义, 通过上一篇的学习, 这些内容基本掌握之后, 现在我们来模拟实现list.
1. ListNode节点类
#pragma once
#include<iostream>
using namespace std;
#include<assert.h>namespace my
{//list的节点类template<class T>struct ListNode{ListNode(const T& val = T()): _prev(nullptr), _next(nullptr), _val(val){}ListNode<T>* _prev;ListNode<T>* _next;T _val;};
2. list的迭代器封装
list的迭代器
迭代器有两种实现方式, 具体应根据容器底层数据结构的实现:
1.原生态指针, 比如:vector
2.将原生态指针进行封装, 因迭代器使用形式与指针完全相同,
因此在自定义的类中必须实现以下方法:1.指针可以解引用,迭代器的类中必须重载operator*()2.指针可以通过->访问其所指空间成员,迭代器类中必须重载operator->()3.指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前或者向后移动,所以需要重载,如果是forward_list就不需要重载--4.迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
template<class T,class Ref,class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;//Ref 和 Ptr类型需要重新定义下,实现反向迭代器时需要用到public:typedef Ref Ref;typedef Ptr Ptr;//构造ListIterator(Node* node = nullptr): _node(node){}//具有指针类似行为Ref operator*(){return _node->_val;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self temp(*this);_node = _node->_next;return temp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int){Self temp(*this);_node = _node->_prev;return temp;}//迭代器支持比较bool operator!=(const Self& l) const{return _node != l._node;}bool operator==(const Self & l) const{return _node == l._node;}Node* _node;};
3. 反向迭代器
//反向迭代器,迭代器复用template<class Iterator>class ReverseListIterator{//注意:此处typename的作用是明确的告诉编译器,Ref是Iterator类中的一个类型,//而不是静态成员变量,否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员//变量,因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;//构造ReverseListIterator(Iterator it): _it(it){}//具有指针类似行为Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){--_it;return *this;}Self& operator++(int){Self temp(*this);--_it;return *this;}Self& operator--(){++_it;return *this;}Self& operator--(int){Self temp(*this);++_it;return temp;}//迭代器支持比较bool operator!=(const Self& l) const{return _it != l._it;}bool operator==(const Self& l) const{return _it == l._it;}Iterator _it;};
4. list类的模拟实现
template<class T>class list{typedef ListNode<T> Node;public://正向迭代器typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, T*> const_iterator;//反向迭代器typedef ReverseListIterator<iterator> reverse_iterator;typedef ReverseListIterator<const_iterator> const_reverse_iterator;//List的构造list(){CreateHead();}list(int n, const T& value = T()){CreateHead();for (int i = 0; i < n; ++i){push_back(value);}}template<class Iterator>list(Iterator first, Iterator last){CreateHead();while (first != last){push_back(*first);++first;}}list(const list<T>& l){CreateHead();//用l中的元素构造临时的temp,然后与当前对象交换list<T> temp(l.begin(), l.end());this->swap(temp);}list<T>& operator=(list<T> l){this->swap(l);return *this;}~list(){clear();delete _head;_head = nullptr;}//list的迭代器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);}reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}//List的容器相关size_t size() const{Node* cur = _head->_next;size_t count = 0;while (cur != _head){++count;cur = cur->_next;}return count;}bool empty() const{return _head->_next = _head;}void resize(size_t newsize, const T* data = T()){size_t oldsize = size();if (newsize <= oldsize){//将有效元素减少到newsizewhile (newsize < oldsize){pop_back();--oldsize;}}else{while (oldsize < newsize){push_back(data);++oldsize;}}}//list的元素访问操作//注意:List不支持operator[]T& front(){return _head->_next->_val;}const T& front() const{return _head->_next->_val;}T& back(){return _head->_prev->_val;}const T& back() const {return _head->_prev->_val;}//list的插入和删除void push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T& val){insert(begin(), val);}void pop_front(){erase(begin());}//在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){Node* pNewNode = new Node(val);Node* pCur = pos._node;//先将新节点插入pNewNode->_prev = pCur->_prev;pNewNode->_next = pCur;pNewNode->_prev->_next = pNewNode;pCur->_prev = pNewNode;return iterator(pNewNode);}//删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){//找到待删除的节点Node* pDel = pos._node;Node* pRet = pDel->_next;//将该节点从链表中拆下来并删除pDel->_prev->_next = pDel->_next;pDel->_next->_prev = pDel->_prev;delete pDel;return iterator(pRet);}void clear(){Node* cur = _head->_next;//采用头删除删除while (cur != _head){Node* next = cur->_next;delete cur;cur = next;}_head->_next = _head->_prev = _head;}void swap(my::list<T>& l){std::swap(_head, l._head);}private:void CreateHead(){_head = new Node;_head->_prev = _head;_head->_next = _head;}Node* _head;};
}
测试代码
对构造函数进行测试
//对模拟实现的list进行测试
//正向打印链表
template<class T>
void PrintList(const my::list<T>& l)
{auto it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;
}//测试list的构造
void Testmylist1()
{my::list<int> l1;my::list<int> l2(10, 5);PrintList(l2);int array[] = { 1,2,3,4,5,6,7,8,9,0 };my::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));PrintList(l3);my::list<int> l4(l3);PrintList(l4);l1 = l4;PrintList(l1);
}

对头/尾插入删除进行测试
//PushBack()/PopBack()/PushFront()/PopFront()
void Testmylist2()
{//测试pushBack和PopBackmy::list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);PrintList(l);l.pop_back();l.pop_back();PrintList(l);l.pop_back();cout << l.size() << endl;//测试pushFront与popFrontl.push_front(1);l.push_front(2);l.push_front(3);PrintList(l);l.pop_front();l.pop_front();PrintList(l);l.pop_front();cout << l.size() << endl;
}

测试指定位置插入删除
//测试insert和erase
void Testmylist3()
{int array[] = { 1,2,3,4,5 };my::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto pos = l.begin();l.insert(l.begin(), 0);PrintList(l);++pos;l.insert(pos, 2);PrintList(l);l.erase(l.begin());l.erase(pos);PrintList(l);//pos指向的节点已经被删除,pos迭代器失效cout << *pos << endl;auto it = l.begin();while (it != l.end()){it = l.erase(it);}cout << l.size() << endl;
}

测试反向迭代器
//测试反向迭代器
void Testmylist4()
{int array[] = { 1,2,3,4,5 };my::list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;const my::list<int> cl(l);auto crit = l.rbegin();while (crit != l.rend()){cout << *crit <<" ";++crit;}cout << endl;
}

list的反向迭代器
我们知道, 反向迭代器的++就是正向迭代器的- -, 反向迭代器的- -就是正向迭代器的++, 因此反向迭代器的实现可以借助正向迭代器, 即: 返现迭代器内部可以包含一个正向迭代器, 对正向迭代器的接口进行包装即可.
//反向迭代器,迭代器复用template<class Iterator>class ReverseListIterator{//注意:此处typename的作用是明确的告诉编译器,Ref是Iterator类中的一个类型,//而不是静态成员变量,否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员//变量,因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;//构造ReverseListIterator(Iterator it): _it(it){}//具有指针类似行为Ref operator*(){Iterator temp(_it);--temp;return *temp;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){--_it;return *this;}Self& operator++(int){Self temp(*this);--_it;return *this;}Self& operator--(){++_it;return *this;}Self& operator--(int){Self temp(*this);++_it;return temp;}//迭代器支持比较bool operator!=(const Self& l) const{return _it != l._it;}bool operator==(const Self& l) const{return _it == l._it;}Iterator _it;};
完
总结
通过以上的实现,可以模拟出一个类似于list的数据结构,并且可以对其中的元素进行添加、删除、查找、等操作。这样就可以在不使用C++内置的list时,使用自己实现的List类来进行相同的操作。
感谢您的点赞与收藏!!!
相关文章:
C++第十二弹 -- STL之list模拟实现
文章索引 前言模拟实现list1. ListNode节点类2. list的迭代器封装3. 反向迭代器4. list类的模拟实现测试代码 list的反向迭代器总结 前言 通过模拟实现可以让我们更加深刻的理解C底层STL的实现逻辑, 本篇就对list的底层进行模拟实现. 博客主页: 酷酷学!!! 点击关注 共同进步!…...
Destiny of Gods首轮测试正式开启,参与玩家数量突破10万
天神风云,波澜再兴,GameFi链游聚合平台Destiny of Gods首款同名数字卡牌回合制游戏首轮测试定档8月20日20:00(GMT8),现已正式开启! 这是一个由人、游灵和神灵共存的世界,历经蛮荒时期的纷争与信…...
QT聊天室基于Tcp
server.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget),server(new QTcpServer(this)) // 给服务器指针对象实例化空间{ui->setupUi(this); }Widget::~Widget() {delete ui; }…...
公开课观后感:密歇根大学python for everyone
从2024年1月17日到2024年8月20日,终于将密歇根大学的python for everyone的python公开课跟完。站在一月份规划的时刻来看,比我想象中花费的时间更多,我当时肯定没有想到要花上整整七个月的时间才能将这个公开课的内容看完,毕竟这个…...
goweb框架-gin
文章目录 Gin框架概览Gin框架的特点Gin框架的安装和基本使用安装基本使用 路由系统路由的基本概念Gin框架路由的特点 Radix Tree(基数树)基数树的定义和原理基数树在Gin框架中的应用节省空间的优化动态路由和通配符处理 路由树的构建注册路由的过程路由树…...
2024年接口测试高频面试题及答案
1. 什么是接口测试? •接口测试就是通过测试不同情况下的入参与之相应的出参信息来判断接口是否符合或满足相应的功能性、安全性要求 •测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑依赖关系 2. 为什么要做接口…...
ESP32-C3在MQTT访问时出现“transport_base: Poll timeout or error”问题的分析(8)
接前一篇文章:ESP32-C3在MQTT访问时出现“transport_base: Poll timeout or error”问题的分析(7) 前边几回分析了笔者在MQTT测试时所遇到的问题: 最终定位到了是由于components\components\tcp_transport\transport_ssl.c的base_poll_write函数中调用的select函数超时返回…...
Linux: 忘记密码的解决方法,passwd
https://www.redhat.com/sysadmin/recover-root-passwd 这里的方法很简单,就是通过console进去,添加一个启动参数,加载sysroot,然后用passwd命令修改密码。这个是RHEL7适用。 https://access.redhat.com/solutions/1192 这个是提…...
36. 有效的数独【 力扣(LeetCode) 】
一、题目描述 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图…...
机器学习中的没有免费午餐定理
嘿,各位机器学习的爱好者们!今天,让我们一起深入探讨机器学习中那个神秘而又重要的概念——没有免费午餐定理。 一、定理引入:探索算法森林的钥匙 在广阔无垠的机器学习领域中,免费午餐定理就如同一把神奇的钥匙&…...
高级java每日一道面试题-2024年8月21日-框架篇[Spring篇]-使用IOC容器应该注意哪些?
如果有遗漏,评论区告诉我进行补充 面试官: 使用IOC容器应该注意哪些? 我回答: 1. 理解IOC的基本概念 控制反转:在传统的编程模式中,程序会主动控制依赖关系的创建和管理。而在IoC容器中,这种控制权被反转给了容器本身。程序员只需要声明…...
LLM训练推理相关概念
1. 有监督微调(Supervised Fine-Tuning)与指令微调(Instruction Fine-Tuning)对模型参数的影响 **有监督微调(Supervised Fine-Tuning, SFT)和指令微调(Instruction Fine-Tuning, Instruct-Tun…...
IP in IP 协议
IP in IP 是一种多重IP协议,即:客户机可以发送一个IP协议内部在嵌套一个IP协议到某个特定的主机上,在由具体的主机作为路由进行转发的协议。 例如: IP in IP帧协议结构为,第一层为发送到IP in IP 路由主机的报文&…...
DAY2: HTTP请求报文和响应报文是怎样的,有哪些常见的字段?| HTTP有哪些请求方式?| GET请求和POST请求的区别
目录 HTTP请求报文和响应报文是怎样的,有哪些常见的字段? 请求报文 响应报文 HTTP有哪些请求方式? GET请求和POST请求的区别 HTTP请求报文和响应报文是怎样的,有哪些常见的字段? HTTP报文分为请求报文和响应报文…...
线性代数:每日一题1/特征值与相似对角化
设A, B 为二阶矩阵,且 AB BA , 则“A有两个不相等的特征值”是“B可对角化"的() A. 充分必要条件 B. 充分不必要条件 C.必要不充分条件 D.既不充分也不必要条件 知识点: 特征向量与特征值的关系 相似矩阵的定义和性质 n阶…...
Android UI:PopupWindow:API
文章目录 类操作 对PopupWindow的操作 创建PopupWindow对象的操作添加并显示PopupWindow的操作移除PopupWindow的操作更新PopupWindow的操作显示内容的相关操作 布局的相关操作进入退出动画的相关操作 Transition设置进入动画的相关操作Transition设置退出动画的相关操作XML设置…...
什么是DevUI?
DevUI是面向企业中后台产品的开源前端解决方案,其设计价值观基于"高效、开放、可信、乐趣"四种自然与人文相结合的理念,旨在为设计师、前端开发者提供标准的设计体系,并满足各类落地场景,是一款企业级开箱即用的产品。 …...
DAY53
作业: 运行1个服务器和2个客户端 实现效果: 服务器和2个客户端互相聊天,服务器和客户端都需要使用select模型去实现 服务器要监视2个客户端是否连接,2个客户端是否发来消息以及服务器自己的标准输入流 客户端要监视服务器是否发来…...
python中len是什么
Python len() 方法返回字符串长度。 len()方法语法: len( str ) 返回值: 返回字符串长度。 以下实例展示了len()的使用方法: #!/usr/bin/python str "this is string example....wow!!!"; print "字符串长度: ", len…...
推荐一个开源的kafka可视化客户端GUI工具(Kafka King)
大佬的博客地址: https://blog.ysboke.cn/posts/tools/kafka-king Github地址: https://github.com/Bronya0/Kafka-King Kafka-King功能清单 查看集群节点列表(完成)支持PLAINTEXT、SASL PLAINTEXT用户名密码认证(完…...
深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...
