【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的功能就要实现三个类:

  1. 模拟实现结点类
  2. 模拟实现迭代器的类
  3. 模拟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*

  1. 在C++中是可以省略struct不写的,也就是说原本的样子应该是 struct ListNode * _prev
  2. 结构体模板或类模板在定义时可以不加 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的使用与模拟实现.


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/24184.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Excel 交叉表的格转成列,行转成格

Excel里交叉表的左表头是卡车号&#xff0c;上表头是工作&#xff0c;交叉格是工作编号。 ABCD1Truck NumberJob1Job2Job3271592859285928372395859282971473297159282971 要求&#xff1a;将交叉格转为列&#xff0c;左表头转为格。 ABC1297139585928272727137371473715726…

深度学习的实用性探究:虚幻还是现实?

深度学习的实用性探究&#xff1a;虚幻还是现实&#xff1f; 深度学习作为人工智能领域的一个热点&#xff0c;已经在学术和工业界引起了广泛的关注。尽管深度学习技术显示出惊人的性能和潜力&#xff0c;但有时它们给人的感觉是“虚”的&#xff0c;或许是因为它们的抽象性和…

微服务框架下,因发送端与消费端的vhost不一致,导致rabbitmq出现严重的消息堆积

一、背景 在生产环境下&#xff0c;rabbitmq机器出现磁盘空间不足的报警&#xff0c;发现是某个队列的消息只有生产&#xff0c;迟迟没有消费。 可以得到的信息是&#xff1a; 队列queue是data_center_file_change_queue队列绑定的交换机是resourceChangeExchange&#xff0c…

PLC通过Profinet转Modbus网关与流量计通讯案例

1、案例背景 在工业自动化系统中&#xff0c;PLC(可编程逻辑控制器)与流量计之间的通信是保证以后设备生产数据准确传输和实现控制功能的关键。但是&#xff0c;由于PLC和流量计可能使用不同的通信协议(如Profinet和Modbus)&#xff0c;因此需要一种转换机制来实现它们之间的通…

uniapp uni-popup内容被隐藏问题

今天开发新需求的时候发现uni-popup 过一会就被隐藏掉只留下遮罩(css被更改了)&#xff0c;作者进行了如下调试。 1.讲uni-popup放入其他节点内 失败&#xff01; 2.在生成dom后在打开 失败&#xff01; 3.uni-popup将该节点在包裹一层 然后将统计设置样式&#xff0c;v-if v-s…

算法题--华为od机试考试(围棋的气、用连续自然数之和来表达整数、亲子游戏)

目录 围棋的气 题目描述 输入描述 示例1 输入 输出 解析 答案 用连续自然数之和来表达整数 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入 输出 解析 答案 亲子游戏 题目描述 输入描述 输出描述 示例1 输入 输出 说明 示例2 输入…

开发人员必备的常用工具合集-lombok

Project Lombok 是一个 java 库&#xff0c;它会自动插入您的编辑器和构建工具&#xff0c;为您的 Java 增添趣味。 再也不用编写另一个 getter 或 equals 方法了&#xff0c;只需一个注释&#xff0c;您的类就拥有了一个功能齐全的构建器&#xff0c;自动化了您的日志记录变量…

低代码:加速企业数字化转型的利器

随着企业数字化转型步伐的加快&#xff0c;低代码开发平台迅速成为市场的焦点。凭借其能简化开发流程、缩短交付时间和降低成本等优势&#xff0c;低代码已经赢得了企业和开发人员的广泛认可&#xff0c;已成为推动企业数字化转型、提高企业创新效率、竞争力的关键工具。本文将…

红酒:如何选择适合的红酒储存容器

选择适合的红酒储存容器对于保持雷盛红酒的品质和风味至关重要。不同的容器具有不同的优缺点&#xff0c;因此应根据个人需求和条件进行选择。以下是一些常见的红酒储存容器的特点和适用场景&#xff1a; 玻璃瓶&#xff1a;玻璃瓶是常见的红酒储存容器。它具有良好的密封性能、…

粘性代理 vs 轮换代理: 特点、优势与选择指南

在网络领域&#xff0c;代理服务器是一种常见的工具&#xff0c;用于隐藏真实IP地址并提供更安全和匿名的网络体验。 粘性代理和轮换代理是两种常见的代理类型&#xff0c;它们在IP持久性和变更频率等方面有所不同。 本文将介绍粘性代理和轮换代理的区别&#xff0c;并分析在…

wordpress里面嵌入哔哩哔哩视频的方法

我们正常如果从blibli获取视频分享链接然后在wordpress里面视频URL插入&#xff0c;发现是播放不了的 而视频嵌入代码直接粘贴呢窗口又非常的小 非常的难受&#xff0c;就需要更改一下代码。你可以在在allowfullscreen"true"的后面&#xff0c;留1个空格&#xff…

GWT 与 Python App Engine 集成

将 Google Web Toolkit (GWT) 与 Python App Engine 集成可以实现强大的 Web 应用程序开发。这种集成允许你使用 GWT 的 Java 客户端技术构建丰富的用户界面&#xff0c;并将其与 Python 后端结合在一起&#xff0c;后端可以运行在 Google App Engine 上。 1、问题背景 在 Pyt…

从零开始手把手Vue3+TypeScript+ElementPlus管理后台项目实战四(引入Axios,并调用第一个接口)

RealWorld接口综述 本项目调用的是RealWorld项目的开放接口。 接口文档如下&#xff1a; https://main--realworld-docs.netlify.app/docs/specs/backend-specs/endpoints https://main--realworld-docs.netlify.app/docs/specs/frontend-specs/swagger RealWorld 是一个适…

C++多线程同步总结

C多线程同步总结 关于C多线程同步 一、C11规范下的线程库 1、C11 线程库的基本用法&#xff1a;创建线程、分离线程 #include<iostream> #include<thread> #include<windows.h> using namespace std; void threadProc() {cout<<"this is in t…

AI产品经理岗位需求量大吗?好找工作吗?

前言 在当今这个科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;已不再仅仅是一个遥远的概念&#xff0c;而是深深嵌入到我们生活的方方面面&#xff0c;从日常的语音助手到复杂的自动驾驶系统&#xff0c;AI的触角无处不在。随着AI技术的广泛应用和持续进…

Docker搭建ELKF日志分析系统

Docker搭建ELKF日志分析系统 文章目录 Docker搭建ELKF日志分析系统资源列表基础环境一、系统环境准备1.1、创建所需的映射目录1.2、修改系统参数1.3、单击创建elk-kgc网络桥接 二、基于Dockerfile构建Elasticsearch镜像2.1、创建Elasticsearch工作目录2.2、上传资源到指定工作路…

python基础实例

下一个更大的数 定义一个Solution类&#xff0c;用于实现next_great方法 class Solution: def next_great(self, nums1, nums2): # 初始化一个空字典answer&#xff0c;用于存储答案 answer {} # 初始化一个空列表stack&#xff0c;用于存储待比较的数字 stack [] # 遍历nu…

短视频评论截取提取软件技术思路

关于开发短视频评论所需要用到得技术第一篇 前言&#xff1a;根据业务需要&#xff0c;我们需要通过短视频找到准客户&#xff0c;这个软件应该是叫短视频评论提取软件&#xff0c;或者是短视频评论采集。无所谓叫什么都可以 进入正题。 第一篇只讲解所应该用到得基础工作 一…

【Java】解决Java报错:ClassCastException

文章目录 引言1. 错误详解2. 常见的出错场景2.1 错误的类型转换2.2 泛型集合中的类型转换2.3 自定义类和接口转换 3. 解决方案3.1 使用 instanceof 检查类型3.2 使用泛型3.3 避免不必要的类型转换 4. 预防措施4.1 使用泛型和注解4.2 编写防御性代码4.3 使用注解和检查工具 5. 示…