【C++】详解STL的容器之一:list

目录

简介

初识list

模型

list容器的优缺点

list的迭代器

常用接口介绍

获取迭代器

begin

end

empty

size

front

back

insert

push_front

pop_front

push_back

pop_back

clear

源代码思路

节点设计

迭代器的设计

list的设计

 begin()

end()

空构造

insert()

push_back

模拟实现

节点设计

迭代器设计

list设计

框架

获取迭代器

空构造

insert

erase

赋值重载

其他接口


简介

C++的STL有六大组件:仿函数空间配置器算法容器迭代器配接器。list属于容器的一种。

list的设计使用了模板,是一种泛型编程。


初识list

模型

list是带哨兵位的双向循环链表

链表是由一个一个的节点通过指针链接起来的。list的节点设计:prev指针指向前一个节点,next指针指向下一个节点,data储存数据。如下图

而list想要实现双向循环链表,只需用一个指针指向不储存数据的节点——哨兵位节点或头节点。让哨兵位节点将整条链表首尾相连。哨兵位的next是链表的头节点,哨兵位的prev是链表的尾节点,如下图

list容器的优缺点

优点:头部插入,头部删除,尾部插入,尾部删除的时间复杂度位O(1)。list的指针是一直指向哨兵位节点的,通过哨兵位节点能找到链表的头节点和尾节点,从而实现头插,头删,尾插,尾删操作。

缺点:对链表中的数据进行排序的时间复杂度会很高。找链表中的某一个数据时,需要遍历链表。

list的迭代器

list的每个节点在内存中储存不是连续的

普通的指针不能完成对链表的遍历——加加指针不能使指针指向下一个节点,减减指针不能使指针指向上一个节点。指针的解引用不能完成对数据存取——普通指针解引用是整个节点,而不是节点里存的数据。

所以不能用普通指针做list的迭代器。应该对普通指针进行封装将封装之后的普通指针作为list的迭代器。该迭代器能完成链表的遍历,数据的存取等操作。

具体怎样封装,下面介绍源代码思路和模拟实现时会详细讲解

迭代器失效:把迭代器指向的节点删除掉,会让该迭代器失效,类似于野指针的问题。在迭代器指向节点的前面或后面插入节点,不会使迭代器失效。


常用接口介绍

获取迭代器

begin

返回哨兵位前一个节点的位置

end

返回哨兵位节点的位置

empty

检测list是否为空,是返回true,否则返回false

size

返回list中有效节点的个数

front

返回list的第一个节点中值的引用

back

返回list的最后一个节点中值的引用

insert

在list的 position 迭代器指向的节点之前插入值为val的节点,返回新插入节点的迭代器

push_front

在链表的头部插入一个值为val的节点

pop_front

删除头部位置的节点

erase

删除position迭代器指向的节点,或删除从first到last迭代器区间的节点,返回下一个位置的迭代器

push_back

在链表的尾部插入值为val的节点

pop_back

删除最后一个节点

clear

删除所有有效节点

源代码思路

下面内容参考侯捷老师《STL源码剖析》

源代码中涉及空间配置器的部分不做重点讲解,只需知道空间配置器是为了给节点list申请空间的即可。

小编摘抄部分源码,带大家了解list设计的大概框架。具体实现的细节在模拟实现时讲解

节点设计

template <class T>
struct __list_node 
{typedef void* void_pointer;void_pointer* prev;void_pointer* next;T data;}

struct在C语言中是结构体,在C++中是类。用struct封装不加访问限定符默认成员是公有的。迭代器和链表需要访问节点的数据,设计成共有是为了方便访问。设计成私有需要声明友元。

prevnext的指针是void*类型是因为不知道数据的类型,源码在实现其他接口时会把void*进行强转。和下面代码的设计是等价的

__list_node<T>* prev;   __list_node<T>* next;

data是用来储存数据的

迭代器的设计

template <class T, class Ref, class Ptr> 
struct __list_iterator  
{typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, Ref, Ptr> self;typedef T value_type;typedef Ptr pointer;typedef Ref reference;typedef __list_node<T>* link_type;link_type node; //节点指针  核心数据//运算符重载,为了让node能像普通指针一样//解引用重载reference operator*()const //Ref{return (*node).data;}//->运算符重载pointer operator->() const  //Ptr{return &(operator*());}//++运算符重载.....//--运算符重载....//==运算符重载....//......
}

成员全部共有,方便list访问

RefPtr两个模板参数是为了区分普通迭代器const迭代器,如下代码

typedef __list_iterator<T, T&, T*>  iterator;   //迭代器别名   
typedef __list_iterator<T, const T&, const T*>  const_iterator; //const迭代器别名

如下图

node是节点的指针,是核心数据。在迭代器这个类中,重载了*  ->  ++  --  ==  != 运算符是为了让结点指针能像普通指针一样,完成对链表的遍历和对数据的存取。这便是封装的魅力。

list的设计

template <class T, class Alloc = alloc>
class list
{
protected:typedef __list_node<T> list_node;public:typedef list_node* link_type;protected:link_type node;
}

上文已经提到,只需用一个节点指针node指向哨兵位节点,便可以通过迭代器对整条链表增删查改。

 begin()

iterator begin()
{
return (link_type)((*node).next);
}

获取哨兵位节点的下一个指针next,但next是void*类型的指针,需要强转成节点类型的指针

end()

iterator end()
{
return node;
}

获取尾节点的下一个节点的指针——哨兵位节点指针

空构造

void empty_initialize()
{node = get_node(); //为哨兵位开空间node->next = node; //没有有效节点,首尾指针都指向自己node->prev = node;}list()//构造函数,构造空链表
{
empty_initialize();}

list是允许构造空链表的

详解构造函数:http://t.csdnimg.cn/Iu0t4

insert()

iterator insert (iterator position, const T& x)
{link_type tmp = create_node(x);tmp->next = position.node;
tmp->prev =  position.node->prev;
(link_type(position.node->prev))->net = tmp;
position.node->prev = tmp;return tmp;}

上述代码中有经过封装的函数。但大致思路如下图

隐式类型转换:http://t.csdnimg.cn/jBT7I

push_back

void push_back(const T& x)
{
insert(end(), x);}

复用insert


模拟实现

目的:源代码的变量经过了嵌套的typedef,函数经过层层封装。用源代码理解list的实现细节和运行机制并不容易。用源代码的的框架和实现思路实现一个简易的list,帮助我们更好的理解list。

模拟实现是用new和delete,管理list的内存。详见内存管理一文:http://t.csdnimg.cn/byO3P

节点设计

template <class T>
struct __list_node
{T* prev;//指向前一个节点T* next;//指向后一个节点T data;//储存数据__list_node(const T& val = T()) //构造函数:prev(nullptr), next(nullptr)  //参数列表, data(val){}
};

迭代器设计

template <class T, class Ref, class Ptr>  //封装指针
struct __list_iterator
{typedef __list_iterator<T, Ref, Ptr>  self;   //迭代器别名 typedef __list_node<T>  list_node; //节点别名别名  list_node* p_node;   //节点指针 __list_iterator(list_node* val)  :p_node(val)        {};     T operator*()//解引用重载{return  p_node->data;        }; Ref operator*()//解引用重载 {return  p_node->data;};T operator->(){return &p_node->data;}Ptr operator->()     {return &p_node->data;}self& operator++()//加加运算符重载 {return p_node->next; };bool operator!=(const self val)//不等于运算符重载{return p_node != val.p_node;};bool operator==(const self val)//等于运算符重载  {return p_node == val.p_node; };};

list设计

框架

template <class T>
class list
{typedef __list_node<T>  list_node; //节点指针别名typedef __list_iterator<T, T&, T*>  iterator;   //迭代器别名   typedef __list_iterator<T, const T&, const T*>  const_iterator;   //const迭代器别名    public://接口......private:list_node* head_node;//头节点,只要一个指针便可代表整个链表 };

获取迭代器

iterator begin() 
{return head_node->next;}
const_iterator begin()
{return head_node->next;
}iterator end()
{return head_node; 
}const_iterator end()  
{return head_node;
}

空构造

void empty_init()
{head_node = new list_node;head_node->_prev = head_node;head_node->_next = head_node;}
list()
{empty_init();
}

insert

iterator insert(iterator pos, const T& x)//指定位置插入
{list_node* cur = pos.p_node;  //用临时指针指向迭代器位置的节点list_node* prev = cur->prev; //用临时指针指向该节点的下一个节点list_node* newnode = new list_node(x); //构造新节点prev->next = newnode; //改变指向newnode->next = cur;cur->prev = newnode;newnode->prev = prev; return newnode; //返回新节点的迭代器
}

代码思路如下图

erase

iterator erase(iterator pos)//指定位置删除
{assert(pos != end());  //不能删哨兵位list_node* cur = pos.p_node; //用临时指针指向前中后三个节点list_node* prev = cur->prev;list_node* next = cur->next;prev->next = next; //改变指向next->prev = prev;delete cur;//删除该节点return next; //返回下一个位置的节点的迭代器}

赋值重载

现在写法

void swap(list<T>& lt)   
{std::swap(head_node, lt.head_node);   
}list<T>& operator=(list<T> lt)  
{swap(lt);return *this;
}

其他接口

void push_back(const T& x)
{insert(end(), x); 
}void push_front(const T& x)
{insert(begin(), x); 
}void pop_back()
{erase(--end()); 
}void pop_front()
{erase(begin()); 
}

本篇内容到此结束啦

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

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

相关文章

使用360绿色清理工具释放磁盘空间

缘起&#xff1a; 配置差的电脑&#xff0c;在尝试安装360安全卫士时&#xff0c;它变得非常卡顿&#xff0c;无法正常使用。我安装360的初衷其实是想定期清理C盘的空间&#xff0c;以优化电脑的性能。 经过一番探索&#xff0c;发现了一个方法&#xff0c;可以单独提取出360…

Docker镜像的创建和Dockerfile

一. Docker 镜像的创建&#xff1a; 1.基于现有镜像创建: &#xff08;1&#xff09;首先启动一个镜像&#xff0c;在容器里做修改docker run -it --name web3 centos:7 /bin/bash #启动容器​yum install -y epel-release #安装epel源yum install -y nginx #安…

物联网小demo

机智云生成代码 具体参考之前的文章 初始化 ADC用来使用光敏电阻 连续采样开启 采样的周期调高 定时器 定时器1用来实现延时 为了只用温湿度模块DHT11 定时器4用来和51进行交互 实现定时的发送和检测心跳信号 IIC 用来使用oled屏幕 USART 串口1和串口2是机智云自己…

ROS是什么

一、ROS通信机制--松耦合分布式通信 1、核心概念 ①节点&#xff08;node&#xff09;---软件模块 ②节点管理器&#xff08;ROS master&#xff09;---控制中心&#xff0c;提供参数管理 ③话题&#xff08;topic&#xff09;---异步通信机制&#xff0c;传输消息&#xf…

【设计模式】13、template 模板模式

文章目录 十三、template 模板模式13.1 ppl13.1.1 目录层级13.1.2 ppl_test.go13.1.3 ppl.go13.1.4 llm_ppl.go13.1.5 ocr_ppl.go 十三、template 模板模式 https://refactoringguru.cn/design-patterns/template-method 如果是一套标准流程, 但有多种实现, 可以用 template …

Py脚本_文件分类

最近发现通过Edge和chrome或者其他浏览器下载的文件都存放在一个地方就很繁琐&#xff0c;于是翻找以前的脚本来归纳这些文件&#xff0c;虽然有IDM下载独有会自动分类&#xff0c;但是相信很多同学都在一个文件里找文件&#xff0c;这次就写个Py脚本来实现这个功能。 # -*- c…

k8s集群安装

目录 部署步骤概览 1、基础环境部署 2、docker环境部署 3、配置k8s集群 4、集群初始化 5、安装dashboard软件 写在前面&#xff1a;本文安装单点master多node的k8s集群&#xff0c;主要用于k8s学习或k8s环境测试&#xff1b;部署的是1.23版本&#xff0c;在1.24版本起&am…

stm32单片机开发四、USART“串口通信“

串口的空闲状态时高电平&#xff0c;起始位是低电平&#xff0c;来打破空闲状态的高电平 必须要有停止位&#xff0c;停止位一般为一位高电平 串口常说的数据为8N1&#xff0c;其实就是8个数据位&#xff08;固定的&#xff09;&#xff0c;N就是none&#xff0c;也就是0个校验…

【Linux 网络】网络基础(一)(局域网、广域网、网络协议、TCP/IP结构模型、网络传输、封装和分用)-- 详解

一、计算机网络的发展背景 1、网络的定义 网络是指将多个计算机或设备通过通信线路、传输协议和网络设备连接起来&#xff0c;形成一个相互通信和共享资源的系统。 &#xff08;1&#xff09; 独立模式 独立模式 &#xff1a; 计算机之间相互独立。 &#xff08;2&#xff09;…

数据结构——二叉树链式结构的实现(下)

二叉树找值找为x的结点 找值不简单吗&#xff1f;轻轻松松拿捏&#xff0c;大部分同学都会写出这样的代码 但这种代码有问题&#xff0c;因为没有把查找结果返回给上一层&#xff0c;没有用返回值接收。 当找到3时返回给了上一层&#xff0c;但并没有用返回值接收&#xff0c;…

Rust 生命周期浅谈

1. 简述 Rust 中的每一个引用都有其 生命周期&#xff08;lifetime&#xff09;&#xff0c;也就是引用保持有效的作用域。大部分时候生命周期是隐含并可以推断的&#xff0c;正如大部分时候类型也是可以推断的一样。类似于当因为有多种可能类型的时候必须注明类型&#xff0c;…

大数据面试题(九):Hive的高频面试考点(值得收藏)

文章目录 Hive的高频面试考点 一、Hive中两个大表实现join的操作,简单描述一下

书接上文,助力智能化诊断高质提效,基于轻量级CNN模型MobileNet开发构建人体手骨X光骨骼骨龄分析识别系统

骨龄是骨骼年龄的简称&#xff0c;需要借助于骨骼在X光摄像中的特定图像来确定。通常要拍摄左手手腕部位的X光片&#xff0c;医生通过X光片观察来确定骨龄。这在临床上是一件非常消耗精力和时间的一项放射临床工作。写一个骨龄可能要10多分钟去完成。如果一天要写几十个骨龄&am…

【汇编语言】中断及外部设备操作

【汇编语言】中断及外部设备操作 文章目录 【汇编语言】中断及外部设备操作前言一、中断及其处理中断的概念8086内中断中断处理程序案例&#xff1a;系统中的0号中断中断过程 二、编制中断处理程序中断处理程序及其结构编制中断处理程序——以除法错误中断为例do0子程序应该放在…

基础I/O--文件系统

文章目录 回顾C文件接口初步理解文件理解文件使用和并认识系统调用open概述标记位传参理解返回值 closewriteread总结 文件描述符fd0&1&2理解 回顾C文件接口 C代码&#xff1a; #include<stdio.h> int main() { FILE *fpfopen("log.txt",&…

《MySQL45讲》读书笔记

重建表 alter table t engine InnoDB&#xff08;也就是recreate&#xff09;&#xff0c;而optimize table t 等于recreateanalyze&#xff0c;让表大小变小 重建表的执行流程 建立一个临时文件&#xff0c;扫描表 t 主键的所有数据页&#xff1b;用数据页中表 t 的记录生…

mac通过termius连接Linux服务器

mac上安装 linux系统 如果有 linux服务器账号密码&#xff0c;那么上一步可忽略&#xff1b; 比如&#xff1a;直接连接阿里云或腾讯云账号 1. 安装termius 链接: https://pan.baidu.com/s/1iYsZPZThPizxqtkLPT89-Q?pwdbw6j 提取码: bw6j 官网 Termius - SSH platform for …

【C++】STL — vector的接口讲解 +详细模拟实现

前言: 本章我们将学习STL中另一个重要的类模板vector… vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。但是又不像数组&#xff0c;它的大小是可以动态改变的本质讲&#xff0c;vector使用动态分配数组来存储它的元素v…

配电室智能巡检机器人

近年来&#xff0c;生产过程高度自动化&#xff0c;各工矿企业关键场所需定期巡检维护。但目前巡检主要靠人工&#xff0c;既耗时费力效率又低&#xff0c;且受环境等因素影响&#xff0c;巡检难以全面规范&#xff0c;隐患或问题易被忽视。在此情况下&#xff0c;如何利用现有…

ElasticSearch02(DSL查询文档,DSL处理结果,RestClient查询,旅游案例,数据聚合)【全详解】

目录 一、DSL查询文档 1. 说明 2. 文本检索 3. 精确查询 4. 地理坐标查询 5. 复合查询 6. 课堂演示 7. 小结 二、DSL处理结果 1. 排序 2. 分页 3. 高亮 4. 课堂演示 5. 小结 三、RestClient查询 1.快速入门 2.match查询 3.精确查询 4.布尔查询 5. 算分函数…