【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,一经查实,立即删除!

相关文章

代码随想录35期Day30-Java

Day30题目 写在前面 五一收假&#xff0c;并且这三道题都是选做&#xff0c;明天看一下吧。 LeetCode332.重新安排行程 &#xff1a;todo LeetCode51. N皇后 class Solution {List<String> path new ArrayList<>();List<List<String>> res new …

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

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

Mybatis扩展

1. Myabtis注解开发 ​ 这几年来注解开发越来越流行&#xff0c;Mybatis也可以使用注解开发方式&#xff0c;这样我们就可以减少编写Mapper映射文件了。我们先围绕一些基本的CRUD来学习&#xff0c;再学习复杂映射多表操作。 1.1 常见注解 Insert&#xff1a;实现新增 Up…

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 …

Leetcode 3132. Find the Integer Added to Array II

Leetcode 3132. Find the Integer Added to Array II 1. 解题思路2. 代码实现 题目链接&#xff1a;3132. Find the Integer Added to Array II 1. 解题思路 这一题由于是统一增加了一个位移&#xff0c;然后再删除了两个元素&#xff0c;因此我们将两个数组进行排序&#x…

MySQL入门学习-使用数据库.创建和删除数据库

MySQL是一种流行的关系型数据库管理系统&#xff0c;可以用于存储和管理大量数据。在MySQL中&#xff0c;可以通过创建和删除数据库来组织和管理数据。 一、关于MySQL中创建和删除数据库的概述&#xff1a; 1. 创建数据库&#xff1a; 在MySQL中&#xff0c;可以使用CREATE …

Py脚本_文件分类

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

XML:基础

一、语法 基本结构&#xff1a; 实例一&#xff1a; <?xml version"1.0" encoding"ISO-8859-1"?> <note data"2008/08/08"> <to>George</to> <from>John</from> <heading>Reminder</heading&…

深入理解Java中的Lambda表达式

Java 8引入的Lambda表达式&#xff08;或称匿名函数&#xff09;是一种简洁优雅的语法&#xff0c;极大简化了开发者编写代码的方式。它可以作为参数传递给方法或赋值给变量&#xff0c;适用于简化代码的场景。本文将详细介绍Lambda表达式的使用&#xff0c;并结合代码实例进行…

C语言 void 指针就是空指针吗?它有什么作⽤?

一、问题 这是⼀个在⾯试时很容易出现的问题&#xff0c;但是也是很多⼈混淆的问题&#xff0c;这个问题如何回答&#xff1f; 二、解答 void 指针⼀般称为通⽤指针&#xff0c;要与空指针严格区分。void 指针⽤于指向⼀个不属于任 何类型的对象&#xff0c;所以 void 指针称为…

k8s集群安装

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

什么是TCP粘包?

TCP粘包 数据的接收和发送是无关的&#xff0c;read()/recv() 函数不管数据发送了多少次&#xff0c;都会尽可能多的接收数据。也就是说&#xff0c;read()/recv() 和 write()/send() 的执行次数可能不同。 举个栗子 write()/send() 重复执行三次&#xff0c;每次都发送字符…

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;…

游戏名台词大赏

文章目录 原神&#xff08;圈内&#xff09; 崩坏&#xff1a;星穹铁道&#xff08;圈内&#xff09; 崩坏3&#xff08;圈内&#xff09; 原神 只要不失去你的崇高&#xff0c;整个世界都会为你敞开。 总会有地上的生灵&#xff0c;敢于直面雷霆的威光。 谁也没有见过风&…

Rust 生命周期浅谈

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