首页 > 其他分享 >STL容器list的模拟实现

STL容器list的模拟实现

时间:2023-11-10 19:01:30浏览次数:43  
标签:容器 const 迭代 器类 STL list 节点 指针

前言

list是C++STL的容器之一,相比string和vector,list在插入和删除数据时的效率更高。因为list的存储模型是一个个节点,新增数据并不需要将旧空间销毁,重新开辟新空间。但是list访问数据的速度较差,因为需要从某个节点开始不断迭代

一、关于list

1.list的大小

用不同的类型分别对库中的list进行实例化,并计算其大小,我们会发现,大小都是12。并且list的存储空间是动态申请,是否插入数据不会影响list的大小,因为sizeof只会计算所占栈空间的大小。如下图

STL容器list的模拟实现_STL容器

2.list的存储结构

容器list的底层是带头双向循环链表。list的存储空间并不是连续的,而是依靠指针联系在一起的一个个节点。如下图

STL容器list的模拟实现_STL容器_02

通过查看stl库中list节点的定义,我们可以清楚的看到其结构。如下图

STL容器list的模拟实现_list_03

每个节点中有三个变量。

指针prev用来指向前一个节点,指针next指向后一个节点,data则用来存储数据

3.list的成员变量

list本身只有一个成员变量,是指向头节点的指针。

因为不管链表有多少个数据,创建了多少个节点,都可以通过头节点迭代到其他任何节点。但是因为list存储空间的不连续,所以不能通过指针来进行迭代,而是需要我们自己实现迭代器类。

但是为了方便计算节点个数,可以新增一个成员变量用来记录节点个数。

4.list的成员函数

4.1Member functions部分函数

STL容器list的模拟实现_STL容器_04

关于构造函数会存在调用歧义的问题,下文会进行说明。

4.2Iterators部分函数

STL容器list的模拟实现_编程_05

迭代器是链表的重点,因为需要我们自己实现,下文会进行详解。

4.3Capacity部分函数

STL容器list的模拟实现_C++_06

4.4Modifiers部分函数

STL容器list的模拟实现_STL容器_07

重点是erase函数存在的迭代器失效问题,下文会进行解释。

二、list的模拟实现

1.节点的实现

节点是自定义类型,除了三个成员变量,还应该有以一个构造函数用来对节点中的两个指针和存储的数据进行初始化。

对于任何新创建的节点,两个指针都置为空,因为链接关系的建立在创建节点之后,因为节点存储的数据类型不确定是内置类型还是自定义类型,因此缺省参数给的默认值写法是匿名对象的写法,这样写可以保证即使T是自定义类型,也能进行初始化。如下图

STL容器list的模拟实现_list的模拟实现_08


2.成员变量的实现

list的底层是带头双向循环链表,list的成员变量只有一个指向头节点的指针,通过头节点指针,就可以迭代到其他任何节点。

STL容器list的模拟实现_C++_09

为了方便获取list的节点个数,为list添加一个size_t _size=0;的成员变量。如下图

STL容器list的模拟实现_C++_10

3.list迭代器的实现

3.1vector中的迭代器

在vector中,普通迭代器和const迭代器就是对普通指针和const指针的重定义。其中模板参数T表示指针所指向内容的类型。如下图

STL容器list的模拟实现_list的模拟实现_11


vector的迭代器本质上就是指针,这是因为vector的存储空间是连续的,指针的++,--可以让vector找到下一个数据的位置。

但是对于list来说,它的存储模型是一个个不连续的节点,指针的++,--不能让list找到下一个或者前一个节点的位置。

3.2list的迭代器类

普通的指针不能用做list的迭代器。list的迭代器需要我们自己封装实现,并且在用法上和vector的迭代器用法完全相同。也就是说要实现一个迭代器类,该类就等同于适用于list的指针。

又因为普通对象和const对象的存在,所以需要实现普通迭代器类和const迭代器类。但实际上可以只实现一个迭代器类,通过模板参数来控制想要实例化出普通迭代器还是const迭代器。

3.3普通迭代器类

如下图

STL容器list的模拟实现_list_12

3.4const迭代器

如下图

STL容器list的模拟实现_list的模拟实现_13

3.5list迭代器类的正确打开方式

普通迭代器和const迭代器之间的区别其实并不大,只不过是const迭代器指向的内容不可以被修改,而普通迭代器指向的内容可以被修改。而这正和operator*,operator->的返回值相关联。

const迭代器的operator*函数和operator->函数的返回值有const修饰,所以指向的内容才不可以被修改,而普通迭代器这两个函数的返回值没有const修饰,所以可以通过迭代器修改指向的内容。

也就是说,只要控制了迭代器类中返回值的类型,就控制了迭代器类实例化出的到底是普通迭代器还是const迭代器。这样我们就只需要写一个迭代器类。没必要像上文一样,写两个迭代器类,并且两个类的实现大同小异,实在太过繁琐。

通过模板参数来迭代器类的返回值。如下图

STL容器list的模拟实现_编程_14

STL容器list的模拟实现_编程_15

迭代器类Iterator的模板参数的解释:

T:控制迭代器指向内容的数据类型

Ptr:控制迭代器函数operator*的返回值

Ref:控制迭代器函数operator->的返回值

list中的普通迭代器和const迭代器。如下图

STL容器list的模拟实现_C++_16

在list中,普通迭代器和const迭代器的参数是确定的。也就是说,迭代器类会分别根据list中普通迭代器和const迭代器所给的模板参数去实例化。

STL容器list的模拟实现_list的模拟实现_17

普通迭代器在迭代器类中的实例化

迭代器类接收的三个模板分别是T,T*,T&

所以Self的类型就是普通迭代器的类型 Iterator<T,T*,T&>,所以Self作为++,--的返回值,没问题。

Ptr的类型是T*,作为普通迭代器operator*的返回类型,可以被修改,没问题。

Ref的类型是T&,作为普通迭代器operator->的返回类型,可以被修改,也没问题。

const迭代器在迭代器类中的实例化

迭代器类接收的三个模板分别是T,const T*,const T&

所以Self的类型就是const迭代器的类型 Iterator<T,const T*,const T&>,所以Self作为++,--的返回值,没问题。

Ptr的类型是const T*,作为普通迭代器operator*的返回类型,不允许被修改,没问题。

Ref的类型是const T&,作为普通迭代器operator->的返回类型,不允许被修改,也没问题。

4.成员函数的实现

4.1Member functions部分函数实现

前情提要:对于带头双向循环链表,每个节点的prev指针指向前一个节点,next指向指向后一个节点。只有头节点时也不例外 ,即使它指向的都是自己。

所以即使是空的list(只有头节点),也要在构造函数中进行初始化,让头节点的前后指针都指向自己。

如下图

STL容器list的模拟实现_list的模拟实现_18

构造函数 一

初始化列表当中的初始化,只能让头指针_head指向的头节点中的_prev和_next置为空,所以需要Init函数来指向自身。如下图

STL容器list的模拟实现_编程_19

STL容器list的模拟实现_C++_20

构造函数 二

作用:将list初始化为有n个val的链表

链接关系:只要让每个节点的_prev指针指向前一个节点,_next指针指向后一个节点即可。如下图

STL容器list的模拟实现_C++_21

STL容器list的模拟实现_list的模拟实现_22

构造函数 三

作用:将list初始化为迭代器所指向链表相同的内容

STL容器list的模拟实现_list的模拟实现_23

STL容器list的模拟实现_list的模拟实现_24

构造函数 四

当有如下list3的初始化时,虽然我们的本意是调用构造函数二,但是在编译器看来构造函数三更加符合int,int类型。所以就会调用构造函数三导致发生错误。如下图

STL容器list的模拟实现_编程_25

STL容器list的模拟实现_编程_26

解决方法:新增一个构造函数,只要将构造函数二的第一个参数的size_t类型改为int即可。如下图

STL容器list的模拟实现_STL容器_27

析构函数:

list的析构函数需要将每个节点各自的空间释放掉。

注意事项:

在释放当前节点之前,需要保存下一个节点的位置,最后释放头节点,并将头指针置空。

STL容器list的模拟实现_编程_28

拷贝构造函数

作用:用一个list对象来初始化另一个list对象

注意事项:每个节点的拷贝都需要动态申请空间,然后才是节点关系的链接

STL容器list的模拟实现_C++_29

如下图

STL容器list的模拟实现_C++_30

赋值运算符重载

赋值运算符重载函数的参数没有用引用,所以会调用拷贝构造,而后交换拷贝构造而来的x,拷贝对象除了作用域后就会销毁。

STL容器list的模拟实现_STL容器_31

STL容器list的模拟实现_list的模拟实现_32

4.2Iterators部分函数的实现

需要注意的是begin返回的是头节点的下一个节点,而end返回的就是头节点。这是因为头节点的下一个节点才是真正存储数据的第一个节点,而头节点才是最后一个存储数据节点的下一个节点。

STL容器list的模拟实现_list的模拟实现_33

STL容器list的模拟实现_STL容器_34

4.3Capacity部分函数的实现

直接利用成员变量_size进行判断即可。如下图

STL容器list的模拟实现_C++_35

STL容器list的模拟实现_编程_36


4.4Modifiers部分函数实现

尾插

创建新节点后,建立链接关系,最后更新链表数据个数。如下图

STL容器list的模拟实现_list_37

尾删

尾删前需要保存前一个节点的位置,删除节点一定要释放空间,建立链接关系后,更新节点个数即可。如下图

STL容器list的模拟实现_STL容器_38

STL容器list的模拟实现_STL容器_39

任意位置插入

在position位置前插入。需要保存当前节点已经当前节点的前一个节点的位置。建立链接关系后更新节点个数。如下图

STL容器list的模拟实现_list的模拟实现_40

STL容器list的模拟实现_编程_41

STL容器list的模拟实现_list_42

任意位置删除

注意事项:头节点不能被删除。删除当前节点前需要先保存前后节点的位置,然后建立链接关系,最后跟新节点个数。如下图

STL容器list的模拟实现_C++_43

STL容器list的模拟实现_STL容器_44

5.删除中的迭代器失效问题

删除迭代器it指向的数据后,it指向的节点已经被销毁,此时对it++,会引发报错。如下图

STL容器list的模拟实现_C++_45

正确的写法是每次删除后重新赋值。如下图

STL容器list的模拟实现_C++_46

6.反向迭代器的实现

反向迭代器是利用正向迭代器来实现的,正向迭代器就是我们自己实现的迭代器类。

模板参数接收的是我们自定义的迭代器类。成员变量也是迭代器类的对象。

反向迭代器的++就是正向迭代器的--,--就是++。

typename的作用是明确告诉编译器,Ref是Iterator类中的类型。如下图

STL容器list的模拟实现_list的模拟实现_47

STL容器list的模拟实现_STL容器_48


标签:容器,const,迭代,器类,STL,list,节点,指针
From: https://blog.51cto.com/u_15466618/8306474

相关文章

  • 无涯教程-批处理 - Listing Folder Contents函数
    列出文件夹内容可以使用dir命令完成,此命令使您可以查看当前目录中的可用文件和目录,dir命令还显示上次修改的日期和时间,以及文件大小DIR[drive:][path][filename][/A[[:]attributes]][/B][/C][/D][/L][/N][/O[[:]sortorder]][/P][/Q][/R][/S][/T[[:]timefield]][......
  • Docker将容器制作成镜像并上传docker hub仓库
    前言在使用docker时候常常需要把自己配置好环境的容器制作成镜像并上传到dockerhub以便之后拉取。本篇文章就是介绍如何将docker容器制作成镜像并上传到dockerhub。背景很多dockerhub上拉下来的镜像,通过修改配置文件等操作,定制成了适合自己的镜像,以后用自己的镜像就可以,因此需要......
  • 解决etcd服务--listen-metrics-urls=http://127.0.0.1:2381
     1、查看etcd的2381端口[root@master-nodemanifests]#netstat-anp|grep2381tcp00127.0.0.1:23810.0.0.0:*LISTEN21765/etcd2、获取etcd的pod[root@master-nodemanifests]#kubectlgetpod-ANAMESPACENAME......
  • docker修改宿主机为容器映射的端口
    1.先关闭容器root1@ubuntu22:~$dockerstop0912.再停止docker服务root1@ubuntu22:~$sudostopdocker3.进入配容器置文件目录修改hostconfig.json文件root@ubuntu22:/var/lib/docker/containers/091302dc373cfa10d414a115276a2a18304052721df6f59c85138......
  • podman 容器管理 docker替代,进阶版本?
    简介Docker的一个缺点是它有一个中央守护进程,它以root用户的身份运行,这对安全有影响。但这正是Podman的用武之地。padman完全兼容docker命令和镜像。Podman是一个无守护进程容器引擎,用于开发、管理和在你的Linux系统上以root或无root模式运行OCI容器。安装安......
  • After page postback, DropDownList item attributes "color" cleared ?
    DropDownList1.SelectedItem.Attributes.Add("style","Color:GREEN")Thisissetwhenapersonclicksabutton,"resubmitorder"...thentheselecteditemisturnedgreensotheyknowthey'vealreadyfixedthatorderitem......
  • mysql 查询报错Expression #1 of SELECT list is not in GROUP BY clause and contain
    这个错误是由于MySQL的新版本中默认开启了ONLY_FULL_GROUP_BY模式,即在GROUPBY语句中的SELECT列表中,只能包含分组或聚合函数,不能包含其他列。而你的查询语句中出现了一个列senior_two.score.student_id,它既没有被分组也没有被聚合,因此MySQL报出了这个错误。5.7版本以下不......
  • SpringBoot复习:(57)ServletRequestListener
    packagecn.edu.tju.confiig;importorg.springframework.context.annotation.Configuration;importjavax.servlet.ServletRequestEvent;importjavax.servlet.ServletRequestListener;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServlet......
  • 通过运行中的容器生成 Docker Compose 配置文件
    背景笔者之前有一次不小心删除了原始的docker-compose.yml文件,不过正在运行的Docker容器还在,找了许久,发现一个方法可以从这些容器中生成一个等效的DockerCompose配置文件。本文将介绍使用autocompose工具从正在运行的容器中反向生成docker-compose.yml文件。安装所需工......
  • 容器云平台No.8~kubernetes负载均衡之ingress-nginx
    容器云平台No.8~kubernetes负载均衡之ingress-nginxIngress是什么?Ingress公开了从集群外部到集群内服务的HTTP和HTTPS路由。流量路由由Ingress资源上定义的规则控制。可以将Ingress配置为服务提供外部可访问的URL、负载均衡流量、终止SSL/TLS,以及提供基于名称的虚......