C++ STL->list模拟实现


theme: smartblue

list
list文档

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高 效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。
    5.与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)

list类的函数接口

namespace ding
{//结点类template<class T>struct _list_node{//构造函数_list_node(const T& val = T());T _data;                 _list_node<T>* _next;   _list_node<T>* _prev;   };//迭代器类template<class T, class Ref, class Ptr>struct _list_iterator{typedef _list_node<T> node;typedef _list_iterator<T, Ref, Ptr> self;//构造函数_list_iterator(node* pnode);  self operator++();self operator--();self operator++(int);self operator--(int);bool operator==(const self& s) const;bool operator!=(const self& s) const;Ref operator*();Ptr operator->();//成员变量node* _pnode;};//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;//Member functionslist();list(const list<T>& lt);list<T>& operator=(const list<T>& lt);~list();//Iterators:iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;//Element access:T& front();T& back();const T& front() const;const T& back() const;//Modifiers: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();//Capacity: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底层采用了带头双向循环链表的结构实现。

image.png
在实现list前,需要定义出一个一个结点出来。直接定义一个结点类,让结点类完成结点的构造即可。

template<class T>
struct _list_node
{_list_node(const T& val = T()):_data(val),_next(nullptr),_prev(nullptr){}T _data;_list_node<T>* _next;_list_node<T>* _prev;
};

迭代器类的实现

  • list底层物理空间不再连续,不再支持[]+下标的方式进行访问

image.png

  • 只能用迭代器进行访问
  • list迭代器的实现不能再像string或者vector那种底层物理空间连续的容器使用原生指针进行实现。
  • 底层空间连续,使用原生指针实现,指针自增或者自减就可以访问到对应的元素,而list由于底层物理空间不来连续的原因,不能再使用原生指针

解决方法

  • 定义一个迭代器类,迭代器相关的操作(比如++,!=,*)等操作但都在迭代器类中重载

结合之前string类和vector类的实现得知迭代器要么就是原生指针,要么就是自定义类型对原生指针的一种封装,去模拟指针的行为。比如对结点指针自增就能指向下一个结点

构造函数

迭代器就是对结点指针进行封装,这里只需要一个结点指针成员变量即可。

_list_iterator(node* node)
{_node = node;
}

++运算符重载

self operator++()
{_node = _node->_next;return *this;
}
self operator++(int)
{self tmp(*this);_node = _node->_next;return tmp;
}
  • 这里的self是经过typedef后的typedef _list_iterator<T, Ref, Ptr> self就是迭代器类类型
  • 前置++与后置++重载语法规定后置++的形参必须为int。
  • 后置++先记录一下当前结点,再让结点指向后一个,返回自增前的即可。

–运算符重载

self operator--()
{_node = _node->_prev;return *this;
}self operator--(int)
{self tmp(*this);_node = _node->_prev;return tmp;
}

*运算符重载

解引用操作符,是想拿到地址的内容,直接返回当前结点的数据内容即可。

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

注意
这里的返回值是Ref,在定义迭代器类是时候定义了三个模板参数,T就是指定的类型,Ref则是指定类型的引用类型,也就是T&,Ptr则是指定类型的指针类型,也就是T*。
在list的实现中,

typedef _list_iterator<T, T&, T*> iterator;
typedef _list_iterator<T, const T&, const T*> const_iterator;
  • STL底层源码就是这样设计的,主要就是为了解决const迭代器的问题,如果不用模板解决的话,可以在定义一个const iterator类也可以解决问题,但是在实现以一个const迭代器与普通迭代器的区别就仅仅只是*返回值类型不一样而已,利用模板参数解决更妙。
  • 普通迭代器返回T&类型,const迭代器返回const T&类型。
  • 这里返回引用类型,主要是因为解引用后可能需要对数据进行修改。

!= && ==

迭代器经常需要进行判断两个迭代器是否相等不相等的操作,这里这需要判断两个迭代器中的结点是否相等即可,不需要做其他操作,定义出成const更为合理。

bool operator==(const self& s) const
{return _node == s._node;
}
bool operator!=(const self& s) const
{return _node != s._node;
}

->操作符重载

这个操作符对于迭代器类型并不是很常用,但是为了模拟指针的行为,指针有->操作符,迭代器就模拟实现了。

运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。也就是这里返回值必须是Ptr 指定类型T*类型。

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

使用->场景:
当list中存放的是自定义类型,

class Date
{
public:Date(int year){_year = year;}int _year = 0;
};
int main()
{std::list<Date>lt;lt.push_back(2023);lt.push_back(2024);auto it = lt.begin();cout << it->_year << endl;return 0;
}

可以使用->访问类的成员变量。

list类的实现

Member functions

构造函数

list()
{_head = new node;_head->_prev = _head;_head->_next = _head;
}

这里的node是经过typedef得。typedef _list_node<T> node;对结点类起的别名
构造一个链表即可,这里得空链表是需要一个头节点得。并且让自己指向自己。

image.png

拷贝构造

申请一个新的头结点,再将源容器中的数据依次尾插到新容器中即可

list(const list<T>& lt)
{_head = new node;_head->_next = _head;_head->_prev = _head;for (auto val : lt){push_back(val);}
}

赋值运算符重载

  • 将原来容器中的数据清空,在依次尾插新元素即可
  • 注意要判断是否自己给自己赋值,不能自己给自己赋值,自己给自己赋值clear后数据丢失了
list<T>& operator=(const list<T>& lt)
{if (this != &lt){clear();for (const auto e : lt){push_back(e);}}return *this;
}

迭代器构造

template<class InputIterator>
list(InputIterator first, InputIterator last)
{_head = new node;_head->_prev = _head;_head->_next = _head;while (first != last){push_back(*first);++first;}
}

析构函数

先清空容器在释放头节点即可

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

clear函数

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

Iterators

begin && end

begin函数返回的是第一个有效数据的迭代器,end函数返回的是最后一个有效数据的下一个位置的迭代器
底层是双向循环链表实现的,所以头结点的下一个就是gebin,头节点就是end。

image.png

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

Modifiers

insert

在pos位置前面插入一个结点

image.png

void insert(iterator pos, const T& x)
{node* newnode = new node(x);//创建新结点node* cur = pos._node;//pos位置的结点指针node* prev = cur->_prev;//pos前一个//连接关系prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;
}

push_back && push_front

  • 尾插 && 头插
  • 在链表的尾部插入一个结点 && 在链表的头部插入一个结点
  • 有了insert和迭代器,直接函数复用即可
//尾插
void push_back(const T& x)
{insert(end(), x);
}
//头插
void push_front(const T& x)
{insert(begin(), x);
}

eraser

删除pos位置的结点

image.png

iterator erase(iterator pos)
{node* cur = pos._node;//当前结点指针node* prev = cur->_prev;//pos位置前一个结点node* next = cur->_next;//pops位置后一个结点//连接关系prev->_next = next;next->_prev = prev;//释放删除的结点delete cur;return iterator(next);//防止迭代器失效,返回pos位置下一个迭代器
}

pop_back && pop_front

  • 尾删和头删
  • 复用迭代器和rease即可
  • end是头节点,尾删时需要–end() 才是尾部结点
void pop_back()
{erase(--end());
}void pop_front()
{erase(begin());
}

Capacity

size

  • 求容器元素个数
  • 遍历容器求个数(效率太低不推荐)
size_t size() const
{size_t size = 0;auto it = begin();while (it != end()){size++;it++;}return size;
}
  • 在定义一个成员变量统计元素个数(以空间换时间,更推荐)

clear

  • 清空list中的结点,除了头结点。
  • 利用迭代器遍历尾删即可
void clear()
{auto it = begin();while (it != end()){pop_back();}
}

empty

bool empty() const
{return _head->_next;
}

resize

  • 扩容加初始化函数
  • resize规则
  • 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
  • 若当前容器的size大于所给n,则只保留前n个有效数据。
void resize(size_t n, const T& val = T())
{size_t sz = size();if (sz < n)//扩容{for (; sz < n; ++sz){push_back(val);}}else//不扩容{if (sz > n)//缩容{int len = sz - n;cout << len << endl;while (len--){pop_back();}}}}

swap函数

交换两个容器的头指针即可

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

参考源码

  • gitee 码云 - 开源中国
  • 欢迎在评论区提出问题或留下你的观点

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

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

相关文章

bpmn-js 事件总线处理

bpmn-js中使用EventBus作为事件的处理句柄&#xff0c;EventBus的使用和我们常规使用的事件总线没啥大的区别&#xff0c;其源码位于&#xff1a;/diagram-js/lib/core/EventBus.js &#xff08;bpmn-js使用diagram-js实现流程图的web端绘制呈现工具&#xff09;。 EventBus使用…

代码随想录算法训练营第三二天 | 买卖股票、跳跃游戏

目录 买卖股票的最佳时机II跳跃游戏跳跃游戏ii LeetCode 122.买卖股票的最佳时机II LeetCode 55. 跳跃游戏 LeetCode 45.跳跃游戏II 买卖股票的最佳时机II 只有一只股票&#xff01; 当前只有买股票或者卖股票的操作。 最终利润是可以分解的&#xff1a;把利润分解为每天为…

变分自编码器(VAE)PyTorch Lightning 实现

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

SAP PP学习笔记- 豆知识02 - 品目要谁来维护?怎么决定更不更新品目的数量金额?

其实都是在品目类型的Customize中设定的。 咱们这里简单试着说一下什么场景使用。 1&#xff0c;SAP中品目有很多View&#xff0c;都要由哪些部门来维护呢&#xff1f; 其实就是谁用谁维护呗。 在新建一个品目的时候&#xff0c;品目Type本身就决定了该品目要由哪些部门来维…

数据结构(4) 链表(链式存储)

链表&#xff08;链式存储&#xff09; 单链表定义基本操作的实现单链表的插入按位序插入指定节点的前插指定节点的后插 单链表的删除 小结 单链表 定义 顺序表优点:可随机存取&#xff0c;存储密度高&#xff0c;缺点:要求大片连续空间&#xff0c;改变容量不方便。 单链表优…

高数总结(4

目录 1.总结&#xff1a;小结&#xff1a; 1.总结&#xff1a; 小结&#xff1a; 关注我给大家分享更多有趣的知识&#xff0c;以下是个人公众号&#xff0c;提供 ||代码兼职|| ||代码问题求解|| 由于本号流量还不足以发表推广&#xff0c;搜我的公众号即可&#xff1a;

Codeforces Round 926 F. Sasha and the Wedding Binary Search Tree

F. Sasha and the Wedding Binary Search Tree 题意 给定一颗二叉搜索树&#xff0c;规定树上的所有点的点权都在范围 [ 1 , C ] [1, C] [1,C] 内&#xff0c;树上的某些节点点权已知&#xff0c;某些节点点权未知&#xff0c;求出合法的二叉搜索树的数量 思路 由于是二叉搜…

这应该是全网第一篇全面解读OpenAI Sora报告的文章,精读报告:Video generation models as world simulators

今天是2024年2月16号&#xff0c;大年初七&#xff0c;年还没过完&#xff0c;早晨起来朋友圈就被Sora刷屏了。本来以为没啥&#xff0c;都是公众号或者视屏啥的&#xff0c;都没点开看&#xff0c;直到看到我导也发了Sora的文章&#xff0c;我就知道这个事情不简单了。 先来看…

支付宝沙箱版模拟网站在线完整支付流程(无营业无费用)内网穿透+局域网测试

文章目录 一、介绍1. 支付2. 支付结果 二、前提准备1. 支付宝开放平台2. 内网穿透3. 局域网 三、order微服务1. 依赖、配置2. 工具类1. 二维码生成2. AlipayConfig 3. 端点代码1. /generatepaycode2. /requestpay3. /payresult4. /receivenotify 环境如下 Version手机安卓支付…

红队笔记Day4 -->多层代理(模拟企业拓扑)

声明&#xff1a;本机文章只用于教育用途&#xff0c;无不良引导&#xff0c;禁止用于从事任何违法活动 前几天的红队笔记的网络拓扑都比较简单&#xff0c;今天就来模拟一下企业的真实网络拓扑&#xff0c;以及攻击方法 一般的大企业的网络拓扑如下&#xff1a;&#xff1a;…

基于Springboot+Vue实现的宿舍管理系统

基于SpringbootVue的宿舍管理系统 1.系统相关性介绍1.1 系统架构1.2 设计思路 2.功能模块介绍2.1 用户信息模块2.2 宿舍管理模块2.3 信息管理模块 3. 源码获取以及远程部署 前言&#xff1a; 在现代教育环境中&#xff0c;学生宿舍的管理显得尤为重要&#xff0c;需要一套能…

如何在Windows中配置多个显示器?这里提供详细步骤

Windows可以通过多种方式使用多个显示器&#xff0c;扩展或复制主显示器。你甚至可以关闭主显示器。以下是如何使用简单的键盘快捷键更改辅助显示设置。 使用WindowsP投影菜单 要快速更改Windows 10处理多个显示器的方式&#xff0c;请按WindowsP。屏幕右侧会弹出一个名为“投…

图表示学习 Graph Representation Learning chapter1 引言

图表示学习 Graph Representation Learning chapter1 引言 前言1.1图的定义1.1.1多关系图1.1.2特征信息 1.2机器学习在图中的应用1.2.1 节点分类1.2.2 关系预测1.2.3 聚类和组织检测1.2.4 图分类、回归、聚类 前言 虽然我并不研究图神经网络&#xff0c;但是我认为图高效的表示…

电脑重装系统之Windows 10 企业版 LTSC 2021

简介 Windows 10 22H2对于我来说太不简洁&#xff0c;最受不了的一点是微软强行硬塞给我一些并没有什么luan用的应用和功能&#xff0c;比如&#xff1a;天气&#xff0c;Onedrive......以及臃肿的ui设计。而且强行进行自动更新&#xff0c;我是真的受不了这个&#xff0c;看着…

leetcode:343.整数拆分

解题思路&#xff1a; 拆分的越多越好&#xff08;暂且认为&#xff09;&#xff0c;尽可能拆成m个近似相等的数&#xff0c;会使得乘积最大 dp含义&#xff1a;将i进行拆分得到最大的积为dp[i] 递推公式&#xff1a;j x dp[i-j](固定j&#xff0c;只通过凑dp[i-j]进而实现所…

optee UTA加载

流程 动态TA按照存储位置的不同分为REE filesystem TA&#xff1a;存放在REE侧文件系统里的TA&#xff1b; Early TA&#xff1a;被嵌入到optee os里的在supplicant启动之前就可用了。 这里我们讲的是常规的存放在REE侧文件系统里的TA。 通过GP标准调用的与TA通信的命令(opens…

【AIGC】Stable Diffusion的ControlNet参数入门

Stable Diffusion 中的 ControlNet 是一种用于控制图像生成过程的技术&#xff0c;它可以指导模型生成特定风格、内容或属性的图像。下面是关于 ControlNet 的界面参数的详细解释&#xff1a; 低显存模式 是一种在深度学习任务中用于处理显存受限设备的技术。在这种模式下&am…

Vue的一些基础设置

1.浏览器控制台显示Vue 设置找到扩展&#xff0c;搜索Vue 下载这个 然后 点击扩展按钮 点击详细信息 选择这个&#xff0c;然后重启一下就好了 ——————————————————————————————————————————— 2.优化工程结构 src的components里要…

MySQL数据库基础第四篇(多表查询与事务)

文章目录 一、多表关系二、多表查询三、内连接查询四、外连接查询五、自连接查询六、联合查询 union, union all七、子查询1.标量子查询2.列子查询3.行子查询4.表子查询 八、事务八、事务的四大特性九、并发事务问题十、事务隔离级级别 在这篇文章中&#xff0c;我们将深入探讨…

数据结构之队的实现

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…