前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【STL】list的模拟实现

【STL】list的模拟实现

作者头像
诺诺的包包
发布2023-10-15 13:59:01
1320
发布2023-10-15 13:59:01
举报
文章被收录于专栏:个人笔记总结个人笔记总结

 放在专栏【C++知识总结】,会持续更新,期待支持

1、list数据结构

list是一个带有头节点的双向链表,list主要是由以下部分组成:list节点类、迭代器类、list本身

1.1、list节点类

关于list节点类,由于list本身是一个双向的链表,所以节点内必须包含指向前一个节点的指针、指向后一个节点的指针、用来存储数据的data。同时我们只需给该类一个构造即可,因为对于节点的析构,我们交给list本身这个类来实现即可。如下所示,为list的节点设计:

1.2、迭代器类

list不能像vector那样以一个原生指针作为迭代器,这是因为list中各个节点并不是连续的,但是list中的迭代器必须要能够像“原生指针”那样,能够指向list的节点,同时能够正确的进行递增、递减、解引用等操作,因此,我们在迭代器类的设计中,必须要重载诸如++、*、->等运算符,使其++能够指向当前节点的下一个节点。同时由于list为双向的链表结构,因此我们还要对--进行重载,使其--指向当前节点的前一个节点。

如下所示,为迭代器类设计的基本结构:

(补充:这里==与!=重载忘记填参数了,在后面实现时才发现,这里前面就懒得改了) 

1.3、list本身

对于list本身来说,成员变量只需要一个头节点的指针,即可表示出整个双向链表,同时list还要提供插入、删除等相关函数接口,以及多个形式构造函数的实现、同时链表节点的释放也是在list析构函数中实现的,并且list还要提供迭代器相关的一些函数,如下所示,为其list基本结构:

接下来,我们对这些接口一一进行模拟实现。

2、模拟实现

上面的节点类我们只需要提供上述的一个构造函数即可,这里我们从迭代器开始,一步步模拟实现。

2.1、迭代器类的模拟实现

2.1.1、迭代器类的模板参数
代码语言:javascript
复制
template<class T,class Ref,class Ptr>

首先,我们来解释为什么要存在三个模板参数,以及这三个模板参数所表示的意义。

这里我们来观察我们在list中typedef出普通迭代器iterator,以及const迭代器const_iterator,如下所示:

代码语言:javascript
复制
typedef _iterator<T, T&, T*> _iterator;
typedef _iterator<T, const T&, const T*> _const_iterator;

在这里,我们与迭代器中的模板参数一一对应,就可以发现,Ref表示的是引用,Ptr表示的是指针类型。

对于普通迭代器,我们在实例化模板参数时,传入T&和T*,我们的_iterator类就会实例化成普通迭代器,Ref这里表示的就是T&,Ptr表示的就是T*,而对于const迭代器来说,在实例化模板参数时,我们传入了const T& 以及const T*,这时_iterator就会实例化成const版本的迭代器,Ref这里表示的就是const T&,Ptr表示的就是const T*

这里我们就实现了:用一份代码,让编译器根据传参的不同,来实例化出普通迭代器或者const迭代器。如果不这样的话,我们就得自己手动再写一份const版本迭代器的相关代码。而const与普通迭代器的代码实现存在大量重复的代码,完全没必要手动再写一份,让编译器来实现即可。使代码更加“优雅”。这也是泛型编程思想的一种体现。

2.1.2、构造函数

迭代器类的构造函数很简单,我们只需要根据传来的参数进行构造即可,如下:

2.1.2、前置++与--

这里我们来实现前置++和--,我们知道,list的节点是不连续的,所以我们要保证++操作可以实现迭代器从当前节点指向下一个节点,--操作可以实现从当前节点指向前一个节点。那该如何实现呢?很简单,利用节点中的next与prev指针来实现:

 2.1.3 后置++与--

前置操作是本身先自增/自减,然后返回自增/自减后的结果,而后置操作则是,本身实现了自增/自减,但是返回的却是自增/自减之前的结果。如下所示:

2.1.4、*与->

我们对迭代器进行解引用,想要实现的是访问到该节点的数据,所以在对*进行重载时,返回节点指向的数据即可,而->返回其节点指向数据的地址即可。 

 2.1.5、==与!=

迭代器在进行比较时,实际上比较的是两个迭代器指向的位置是否为同一个位置,而不是比较迭代器的大小,这是错误的。而判断是否指向同一个位置,只需要判断其指向的pnode是否相同即可。

 至此,我们的迭代器算是正是完成了,接下来我们对list中的接口进行完善。

 2.2、list的模拟实现

在构造相关方面,由于这几个构造都会先创建出一个带有头节点的空链表,所以这里我们对其进行封装成一个函数,然后由不同的构造函数进行调用。如下:

2.2.1、空构造

空构造的实现很简单,就直接调用上面的empty_init函数即可表示是个空链表。

 2.2.2、构造n个val

实际上就是尾插n次,每次插入val即可。如下,一个for循环搞定:

 2.2.3、拷贝构造

对于拷贝构造,也有两种写法,传统写法以及现代写法,传统写法实际也很简单,创建一个头节点,然后将待拷贝的list遍历一遍,将数据进行push_back即可:

 现代写法也很简单,构造一个临时变量,然后进行swap即可。如下:

 2.2.4、迭代器区间构造

2.3、赋值运算符重载

对于赋值运算符重载,也存在两种写法,现代写法以及传统写法,对于传统写法,我们的思路同样是遍历链表,然后实现尾插。如下:

 现代写法就很巧妙,与vector相同,我们采用传值传参(相比传引用,会多一次拷贝构造),然后进行swap即可。

 2.4、迭代器相关

2.4.1、begin与end

begin指向第一个有效节点,而end指向最后一个有效节点的下一个节点。

 当然,我们也要提供const版本,如下:

2.5、元素访问相关

2.5.1、front与back

front返回头部有效节点的数据,也就是head的下一个节点的数据,back返回尾部节点的数据,即head的前一个节点的数据。同样我们也要支持const版本:

2.6、插入删除操作

 2.6.1、insert与erase

insert实现在pos位置插入,list的插入效率非常高,达到了常数级别,我们只需要new一个节点来存储待插入的数据,然后修改pos前一个位置的next以及pos位置的prev指针,以及待插入节点的两个指针即可。如下;

 erase则实现删除pos位置的节点,在删除时要注意一点,就是不能删除头节点,因此我们在erase之前,要先对pos进行判断。如下:

 另外,erase我们还提供了迭代器区间删除版本,实际上就是对erase(pos)的复用:

 2.6.2、push_back与pop_back

push_back实现尾插,不过由于上面我们实现了insert,对任意位置可以进行插入,因此在这里我们只需要进行复用insert即可。如下所示:

 同理,对于pop_back尾删操作,我们也可以进行复用erase:

 2.6.3、push_front与pop_front

同理,我们对于头插头删继续复用insert与erase:

2.7、其它函数

2.7.1、swap

swap函数实现两个链表进行交换,我们直接使用std中提供的swap函数即可:

2.7.2、size

size返回有效节点的个数,这里我们用指针相减法是实现不了的,因为list中的节点不是连续的,所以这里我们只能定义一个变量,然后遍历链表进行++。:

 2.7.3、resize

resize与vector中的模拟实现相同,有两种情况,“扩容”以及缩容。当resize(n,val)中的n>当前size时,进行扩容,扩容多出来的节点,用val填充其data。否则缩容。如下:

2.7.4、clear

clear用来清空容器,如下所示,我们借助begin与end遍历链表,复用erase即可:

 2.7.5、empty

empty用来判断容器是否为空,这里只有当begin与end相等时,容器才为空:

2.8、析构函数

我们上方已经实现了一个clear,用来清空容器有效元素,我们在析构中可以进行复用,最后我们再把head节点释放即可。

2.9、补充

这里我们后面在测试时发现,我们的迭代器区间构造,以及构造n个val,这两者之间发生了冲突:

 解决方法也很简单,有两种,第一种就是我们在传参时把10再转成size_t 类型,让它与其构造的模板参数类型保持一致,都是size_t,此时就不会调错函数了。第二种就是再提供一个int版本的构造。

 此时便可以解决该问题。

至此,我们的list基本上算是模拟实现完毕,还剩一个反向迭代器,将放在后面章节进行讲解。这里我在记录博客时都是以图片的形式进行上传,主要是个人觉得如果放代码的话,显得很冗余,也不便观察,不如图片清晰明了。代码都放在个人gitee:点击跳转

end.

生活原本沉闷,但跑起来就会有风!🌹

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、list数据结构
    • 1.1、list节点类
      • 1.2、迭代器类
        • 1.3、list本身
        • 2、模拟实现
          • 2.1、迭代器类的模拟实现
            • 2.1.1、迭代器类的模板参数
            • 2.1.2、构造函数
            • 2.1.2、前置++与--
            •  2.1.3 后置++与--
            • 2.1.4、*与->
            •  2.1.5、==与!=
          •  2.2、list的模拟实现
            • 2.2.1、空构造
            •  2.2.2、构造n个val
            •  2.2.3、拷贝构造
            •  2.2.4、迭代器区间构造
          • 2.3、赋值运算符重载
            •  2.4、迭代器相关
              • 2.4.1、begin与end
            • 2.5、元素访问相关
              • 2.5.1、front与back
            • 2.6、插入删除操作
              •  2.6.1、insert与erase
              •  2.6.2、push_back与pop_back
              •  2.6.3、push_front与pop_front
            • 2.7、其它函数
              • 2.7.1、swap
              •  2.7.3、resize
              • 2.7.4、clear
              •  2.7.5、empty
            • 2.8、析构函数
              • 2.9、补充
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档