C++初阶-list的模拟实现(难度较高)

1.list(容器,不是类)模拟实现基本结构

这个实现是和源码的实现不同的,不要进行强制类型转换了,很麻烦。我们把定义放在.h文件中,在.cpp中放测试代码。

注:这个基本结构之后会改变,到时候再讲原因。

//.h
#include<iostream>
#include<list>
#include<assert.h>
using namespace std;
namespace td
{template<class T>struct list_node{public:list_node<T>* _next;list_node<T>* _prev;T _data;};template<class T>class list{public:typedef list_node<T> Node;private:Node* _head;};
}
//.cpp
#include"test.h"
namespace td
{ 
void test()
{ }
}
int main()
{return 0;
}

2.list的无参构造函数的实现

这个很简单,我们直接先申请一个Node大小的空间,然后再改变指向即可:

//.h
list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}

3.list_node的有参构造函数的实现

这个函数比较简单,用初始化列表即可,但是,注意:我们这个构造有参是因为要把数据存储起来,一般建议:给一个缺省值,这样让它更好的进行初始化。在C++中一般给T()即可:

//.h
list_node(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x)
{ }

4.list::push_back的实现

这个尾插难度不是很高,只要注意改变指向即可:

//.h
void push_back(const T& val)
{Node* newnode = new Node(x);Node* tail = _head->_prev;newnode->_prev = tail;tail->_next = newnode;newnode->_next = _head;_head->_prev = newnode;
}

这个代码确实有些难看懂,主要是我们对双向链表了解不是很全面,首先tail是尾结点,我们尾插是在尾结点之后插入结点,然后再把另外一些的指向改成正确的,这样就没太大的问题了。

5.list_iterator类的基本结构

我们无法用typedef Node* iterator的方式来得到迭代器,因为我们无法遍历,如果std::list<int> lt1;std::list<int>::iterator it1=lt1.begin();其中如果*it1那么本质上可以理解*it1为:it1.operator*();而在这里面解引用是用_node->_data的方式实现的。所以我们需要用迭代器,这个迭代器是被封装的一个类,那么基本结构为:

template<class T>
struct list_iterator
{typedef list_node<T> Node;Node* _node;
};

5.1list_iterator类的基本函数实现

一般包括但不限于*、++、--、==、!=这些运算符,还有->,这里一并实现了:

list_iterator(Node* node):_node(node)
{ }
T& operator*()
{return _node->data;
}
//前置++
list_iterator<T>& operator++()
{_node = _node->next;return *this;
}
bool operator!=(const list_iterator<T>& it)
{return _node != it._node;
}
bool operator==(const list_iterator<T>& it)
{return _node == it._node;
}

5.2list_iterator::operator->()的实现

这里先讲它的写法:

T* operator->()
{return &_node->_data;
}

为什么能这样写?

假设:

struct A
{int _a1;int _a2;
};

如果在我们std::list<A> lt;auto it=lt.begin();如果it->_a1,

那么实际上它会转化为it.operator->()->_a1也就是说,之前我们应当写为:it->->_a1的,但是,为了可读性,编译器做了特殊处理,省略了一个->。所以能这样写。

6.如何实现list_const_iterator

我们无法在list_iterator里面加一个const迭代器版本,因为这样并不能作为重载,因为返回值不同并不能作为重载,比如begin它在普通迭代器就要返回值为iterator,那么如果是const迭代器就要返回值为const_iterator,实际上就是:const T,所以我们只能自己写一个额外的迭代器,那么我们可以这样写:

template<class T>
struct list_const_iterator
{typedef list_node<T> Node;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}list_const_iterator<T>& operator++(){_node = _node->_next;return *this;}bool operator!=(const list_const_iterator<T>& it){return _node != it._node;}bool operator==(const list_const_iterator<T>& it){return _node == it._node;}
};

我们发现大部分只是返回值不一样而已,所以这种方式确实不是那么好,所以我们可以把它再写为模板的形式。

7.list_iterator类的改进结构

为了适应多种迭代器,所以我们可以把它写为模板形式,不过如果真再写一个模板我们是没有思路的,所以我们可以改成如下形式(先看一下,理解难度有些高):

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){}Ref operator*(){return _node->_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;}
};

这个结构就是改进后的结构,添加的两个模板参数其中Ref意思是:引用,Ptr的意思是:指针。所以之后传递的时候就要这样传递。其次,我们把之前写为list_iterator的部分都改为Self。这里的++、--有后置与前置之分,所以我们要注意它的写法,这里就不做过多讲解了。

8.list类的改进结构

通过这些改变,我们可以加上这些:

typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&,const T*> const_iterator;

9.list::begin、list::end的实现

这两个函数都有const迭代器版本和普通迭代器版本,我们要注意的是,如果是begin,那么就是_head->_next,因为头结点是哨兵位,不存储数据;如果是end那么就是哨兵位,因为end这个位置是不存储数据的,可以认为这是尾结点的后面一个指针!!!所以我们可以写出:

//list
iterator begin()
{return _head->_next;
}
iterator end()
{return _head;
}
const_iterator begin() const
{return _head->_next;
}
const_iterator end() const
{return _head;
}

10.list的增删查改的实现

这些增删查改相对于我们之前的数据结构中的双向链表没什么太大的区别,如果想要更加深入了解代码生成的原因,可以看我之前的博客:

数据结构初阶-双向链表-CSDN博客文章浏览阅读524次,点赞9次,收藏7次。为结构体,其中包含一个指针指向下一个结点的结构体指针next,一个指针指向上一个结点的指针prev,还有所存储的数据data,由于存储的数据类型未知,所以我们typedef int LTDataType;若结果为8 -> 6 -> 4 -> 2 -> 3 -> 1 -> 2 -> 3 -> 4 ->则代码无误,如果感兴趣的话也可以写一个在pos位置之前插入数据的函数。如果结果为8 -> 6 -> 4 -> 2 -> 3 -> 1 -> 2 -> 3 -> 4 ->//存储下一个结点的地址。 https://blog.csdn.net/2401_86446710/article/details/145201866?sharetype=blogdetail&sharerId=145201866&sharerefer=PC&sharesource=2401_86446710&sharefrom=mp_from_link那么这些东西比较简单,我这里就只讲解一些比较重要的代码:

//list
//在pos位置之前插入
void insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//这不是唯一的写法//但是一定要改变四个指针的指向prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}
//清除pos位置的数据,并返回清除后该位置的指针
iterator erase(iterator pos)
{//防止链表为空assert(pos != end());Node* cur = pos._node;Node* nextNode = cur->_next;Node* prevNode = cur->_prev;//只要改变两个指针的指向即可prevNode->_next = nextNode;nextNode->_prev = prevNode;delete cur;//强制类型转换return iterator(nextNode);
}
void push_back(const T& x)
{insert(end(), x);
}
void push_front(const T& x)
{insert(begin(), x);
}
void pop_front()
{erase(begin());
}
void pop_back()
{//end位置没有数据,所以要清除end位置前面一个位置的数据erase(--end());
}

11.list::size的实现

这个函数的实现可能需要先加一个_size到成员变量里面,所以可以写出:

//list
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(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}//void push_back(const T& val)//{//	Node* newnode = new Node(x);//	Node* tail = _head->_prev;//	newnode->_prev = tail;//	tail->_next = newnode;//	newnode->_next = _head;//	_head->_prev = newnode;//}iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}//在pos位置之前插入void insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);//这不是唯一的写法//但是一定要改变四个指针的指向prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;}//清除pos位置的数据,并返回清除后该位置的指针iterator erase(iterator pos){//防止链表为空assert(pos != end());Node* cur = pos._node;Node* nextNode = cur->_next;Node* prevNode = cur->_prev;//只要改变两个指针的指向即可prevNode->_next = nextNode;nextNode->_prev = prevNode;delete cur;--_size;//强制类型转换return iterator(nextNode);}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){//end位置没有数据,所以要清除end位置前面一个位置的数据erase(--end());}size_t size() const{return _size;}
private:Node* _head;size_t size;
};

12.list::clear的实现

这个函数我们可以知道,直接先erase后返回值是有效的,所以我们不用怕it是野指针,所以我们可以这样写:

void clear()
{iterator it = begin();while (it != end()){it = erase(it);}
}

由于erase已经delete了pos位置的结点(原it位置),所以直接这样写就可以了。

13.list::~list的实现

这个函数只要先调用clear函数后再把哨兵位的空间释放,_size置为0即可:

~list()
{clear();delete _head;_head = nullptr;_size = 0;
}

14.list::swap的实现

这个函数实现不是很难,所以直接写了:

void swap(list<T>& lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}

15.list的拷贝构造函数的实现

我们和list底层相似的方式进行完善:

void empty_init()
{//哨兵位_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
list(const list<T>& lt)
{empty_init();for (auto e : lt){push_back(e);}
}

16.list::operator=的实现

这个函数也是比较简单的实现,但是要注意:我们不能给形参加上引用,因为那样是比较麻烦的写法:

list<T>& operator=(list<T> lt)
{swap(lt);return *this;
}

17.list::list(initializer_list<T> lt)的实现

这个函数实现不是很难,我们直接先构造一个头结点,再和拷贝构造一样尾插即可:

list(initializer_list<T> lt)
{empty_init();for (auto& e : lt){push_back(e);}
}

18.测试

我们用以下代码进行测试:

namespace td
{void test(){td::list<int> l1 = { 5,6,3,6,1,9 };td::list<int> l2 = l1;td::list<int> l3(l1);td::list<int> l4;l4 = l1;for (const auto& e : l1){cout << e << " ";}cout << endl;for (const auto& e : l2){cout << e << " ";}cout << endl;for (const auto& e : l3){cout << e << " ";}cout << endl;for (const auto& e : l4){cout << e << " ";}cout << endl;l1.push_back(3);l2.pop_back();l3.push_front(8);l4.pop_front();for (const auto& e : l1){cout << e << " ";}cout << endl;for (const auto& e : l2){cout << e << " ";}cout << endl;for (const auto& e : l3){cout << e << " ";}cout << endl;for (const auto& e : l4){cout << e << " ";}cout << endl;}
}
//struct A
//{
//	int _a1;
//	int _a2;
//};
int main()
{td::test();return 0;
}

那么最终运行结果为:

如果有人想测试一下insert和erase,可以用一个迭代器通过begin()和end()进行++、--的操作,进行插入删除测试即可。

19.总结

list的模拟实现已经全部讲解完,有些不常用的函数如:remove_if、splice这些感兴趣的可以自己去实现一下。std::list已经全部讲完了,下讲将讲解:stack和queue(栈和队列)。喜欢的可以一键三连哦,下讲再见!

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

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

相关文章

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…

【iOS】cell的复用以及自定义cell

【iOS】cell的复用以及自定义cell 文章目录 【iOS】cell的复用以及自定义cell前言cell的复用手动&#xff08;非注册&#xff09;自动&#xff08;注册&#xff09; 自定义cell 前言 cell的复用及自定义cell是UITableView或UICollectionView的一个重要优化机制,当用户滚动视图…

深度学习之模型压缩三驾马车:基于ResNet18的模型剪枝实战(2)

前言 《深度学习之模型压缩三驾马车&#xff1a;基于ResNet18的模型剪枝实战&#xff08;1&#xff09;》里面我只是提到了对conv1层进行剪枝&#xff0c;只是为了验证这个剪枝的整个过程&#xff0c;但是后面也有提到&#xff1a;仅裁剪 conv1层的影响极大&#xff0c;原因如…

传输层协议:UDP

目录 1、概念 2、报文结构 3、核心特性 3.1 无连接 3.2 不可靠交付 3.3 面向数据报 3.4 轻量级&高效 3.5 支持广播和组播 4、典型应用场景 5、优缺点分析 6、与TCP的区别 1、概念 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09…

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…

【汇编逆向系列】七、函数调用包含多个参数之浮点型- XMM0-3寄存器

目录 1. 汇编代码 1.1 debug编译 1.2 release编译 2. 汇编分析 2.1 浮点参数传递规则 2.2 栈帧rsp的变化时序 2.3 参数的访问逻辑 2.4 返回值XMM0寄存器 3. 汇编转化 3.1 Debug编译 3.2 Release 编译 3.3 C语言转化 1. 汇编代码 上一节介绍了整型的函数传参&#x…

华为云Flexus+DeepSeek征文 | 从零到一:用Flexus云服务打造低延迟联网搜索Agent

作者简介 我是摘星&#xff0c;一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型&#xff0c;将实际使用经验分享给大家&#xff0c;希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 作者简介 前言 1. 项目背景与技术选型 1.1 项目…

【多智能体】受木偶戏启发实现多智能体协作编排

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本专栏《人工智能》旨在记录最新的科研前沿&#xff0c;包括大模型、具身智能、智能体等相关领域&#xff0c;期待与你一同探索、学习、进步&#xff0c;一起卷起来叭&#xff…

Java八股文——Spring篇

文章目录 Java八股文专栏其它文章Java八股文——Spring篇SpringSpring的IoC和AOPSpring IoC实现机制Spring AOP实现机制 动态代理JDK ProxyCGLIBByteBuddy Spring框架中的单例Bean是线程安全的吗&#xff1f;什么是AOP&#xff0c;你们项目中有没有使用到AOPSpring中的事务是如…

NineData数据库DevOps功能全面支持百度智能云向量数据库 VectorDB,助力企业 AI 应用高效落地

NineData 的数据库 DevOps 解决方案已完成对百度智能云向量数据库 VectorDB 的全链路适配&#xff0c;成为国内首批提供 VectorDB 原生操作能力的服务商。此次合作聚焦 AI 开发核心场景&#xff0c;通过标准化 SQL 工作台与细粒度权限管控两大能力&#xff0c;助力企业安全高效…

开源技术驱动下的上市公司财务主数据管理实践

开源技术驱动下的上市公司财务主数据管理实践 —— 以人造板制造业为例 引言&#xff1a;财务主数据的战略价值与行业挑战 在资本市场监管日益严格与企业数字化转型的双重驱动下&#xff0c;财务主数据已成为上市公司财务治理的核心基础设施。对于人造板制造业而言&#xff0…

借助它,普转也能获得空转信息?

在生命科学研究领域&#xff0c;转录组技术是探索基因表达奥秘的有力工具&#xff0c;在疾病机制探索、生物发育进程解析等诸多方面取得了显著进展。然而&#xff0c;随着研究的深入&#xff0c;研究人员发现普通转录组只能提供整体样本中的基因表达水平信息&#xff0c;却无法…

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…

Java事务回滚详解

一、什么是事务回滚&#xff1f; 事务回滚指的是&#xff1a;当执行过程中发生异常时&#xff0c;之前对数据库所做的更改全部撤销&#xff0c;数据库状态恢复到事务开始前的状态。这是数据库“原子性”原则的体现。 二、Spring 中的 Transactional 默认行为 在 Spring 中&am…

云灾备数据复制技术研究

云灾备数据复制技术&#xff1a;数字时代的“安全气囊” 在当今信息化时代&#xff0c;数据就像城市的“生命线”&#xff0c;一旦中断&#xff0c;后果不堪设想。想象一下&#xff0c;如果政务系统突然崩溃&#xff0c;成千上万的市民服务将陷入瘫痪。这就是云灾备技术的重要…

如何处理Shopify主题的显示问题:实用排查与修复指南

在Shopify店铺运营过程中&#xff0c;主题显示问题是影响用户体验与品牌形象的常见痛点。可能是字体错位、图片无法加载、移动端显示混乱、功能失效等&#xff0c;这些都可能造成客户流失和转化下降。 本文将从问题识别、原因分析、修复方法到开发者建议全方位解读如何高效解决…

前端监控方案详解

一、前端监控方案是什么&#xff1f; 前端监控方案是一套系统化的工具和流程&#xff0c;用于收集、分析和报告网站或Web应用在前端运行时的各种性能指标、错误日志、用户行为等数据。它通常包括以下几个核心模块&#xff1a; 性能监控&#xff1a;页面加载时间、资源加载时间…

Camera相机人脸识别系列专题分析之十二:人脸特征检测FFD算法之libvega_face.so数据结构详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; Camera相机人脸识别系列专题分析之十一&#xff1a;人脸特征检测FFD算法之低功耗libvega_face.so人脸属性(年龄&#xff0c;性别&#xff0c;肤…

如何配置HarmonyOS 5与React Native的开发环境?

配置 HarmonyOS 5 与 React Native 的开发环境需遵循以下步骤 一、基础工具安装 ‌DevEco Studio 5.0‌ 从 HarmonyOS 开发者官网 下载安装勾选组件&#xff1a; HarmonyOS SDK (API 12)ArkTS 编译器JS/ArkTS 调试工具HarmonyOS 本地模拟器 ‌Node.js 18.17 # 安装后验证版…