STL源码刨析:序列式容器之list

目录

        1.前言

        2.list的节点定义和结构

        3.list的迭代器定义和结构

        4.list的定义和结构

        5.list的内存管理        

        6.list的元素操作


        前言

        在刨析了vector容器的源码后,list容器相比与vector容器,其元素的插入和删除较快,不需要对原本容器中的元素进行排序,因此针对list容器的每一次插入和删除都是常数级。而且list对于内存的申请属于是需要几个内存就申请几个内容,而vector申请的内存通常为当前内存的两倍。本章将对list进行讲解,至于何时使用vector,何时使用list更是要区别于元素的多少,具体的业务场景。


list的节点定义和结构

        list的结构属于环状双向链表的结构(PS:环状双向链表,指的是链表的头指针指向尾节点,链表的尾指针指向头节点,故称环状双向链表),应此在实现list时,我们还需要针对其list中的每一个节点进行设计。在设计list节点时,要实现list双向链表的特性,我们还需要两个指针,一个头指针一个尾指针,且每一个节点需要存储值,故list节点设计代码如下:

//list节点设计代码
template <class T>
struct _list_node{typedef void* void_pointer;//设计为空指针方便类型转换,也可以设计为_list_node<T>*void_pointer prev;//头指针void_pointer next;//尾指针T data;           //存储值
}

list的迭代器定义和结构

        相比于vector的随机迭代器,list提供的迭代器为双向迭代器,因为list不支持下标操作。而且vector在扩充元素时,若内存空间不足则需要向系统申请新的空间,并把旧元素复制到新的空间上,这会导致迭代器生效(类似于虚吊指针,也可以叫悬空指针)。但是list的插入操作都只是修改节点的头指针和尾指针的指向空间,应此不会导致迭代器生效。在了解list迭代器类型后,其设计的源码如下:

//list的迭代器设计源码
template<class T, class Ref, class Ptr>    //Ref代表解引用时返回引用类型,Ptr则是指针类型
struct _list_iterator{typedef _list_node<T>* link_type;    /list节点指针link_type node;    //变量node指向list节点typedef _List_iterator<T, Ref, Ptr> self;      //当前实例对象的别名typedef _list_iterator<T, T&,T*> iterator;    //当前实例对象的迭代器typedef bidirectional_iterator_tag iterator_category;    //封装双向迭代器//以下为迭代器常用封装,Traits编程方法typedef T value_tyoe;typedef Ptr pointer;typedef Ref reference;typedef size_t size_type;typedef ptrdiff_t difference_type;
}

        在知道list迭代器为双向迭代器后,我们知道双向迭代器的特点,即不支持下标操作,仅支持迭代器的累加,递减,解引用和判断节点值是否相等的特性,为了实现这些特性,其迭代器还需要实现以下功能:

//list迭代器支持的操作的实现
_list_iterator(){}
_list_iterator(link_type x) : node(x){}
_list_iterator(const iterator& x) : node(x.node){}bool operator==(const self& x) const {    //判断是否相等return node == x.node;
}bool operator!=(const self& x) const {    //判断是否不等return node != x.node;
}pointer operator->() const { return &(operator*()); } //取值(左值)reference operator*() const { return (*node).data; }    //取值(右值)//以下为累加的实现
self& operator++(){node = (link_type)((*node).next);return *this;
}self& operator++(int){self temp =*this;++(*this);return tem;
}//以下为递减的实现
self& operator--(){node = (link_type)((*node).prev);return *this;
}self& operator--(int){self tmp = *this;--(*this);return tmp;
}

list的定义和结构

        在了解list节点和迭代器的定义和结构后,我们还提到list是一种环状的双向链表,而由于独特的环状结构,我们在设计该双链表时需要插入一个空的节点(PS:是为了满足STL的前闭后开的要求,但是我觉得更多的是为了方便判断是否遍历完整个链表而设计的节点)。在满足设计的结构的基础上,我们在实现list的结构时,还需要定义形如begin(),end(),empty(),size(),font()和back()等操作函数,故list结构大致如下:

//list的结构实现
template<class T, class Alloc = alloc>
class list{
protected:typedef _list_node<T> list_node;typedef simple_alloca<list_node,Alloc> list_node_allocator;//封装迭代器,内存管理中使用
public:typedef list_node* link_type;
protected:link_type node;    //节点指针,指向链表的头节点便可表示整个链表
}iterator begin() { return (link_type)((*node).next); }    //获取头节点iterator end() { return node; }    //获取尾节点bool empty() const { return node->next == node; }    //判断是否为空节点size_type size() const{    //计算容器中元素的个数size_type result = 0;    distance(begin(), end(), result);//遍历容器return result;    
}reference front() { return *begin(); }    //取头节点的值rederence back() { return *(--end()); }   //取尾节点的值

        针对list的环状双向链表,可以参考下图:

图1.listD的环状双向链表结构图


list的内存管理        

        参考vector容器的讲解,其list内部也存在应该迭代器,为了实现内存的精准控制,其迭代器也是专门定义了一个(参考小节:list的迭代器定义和结构),为了实现内存的管理,我们需要满足内存的分配,释放等操作,在这些操作基础上还需要满足带值的节点的内存申请,对此list于内存相关的代码如下:

//list内存管理源码实现
link_type get_node(){ return list_node_allocator::allocate(); }   //申请一个节点link_type put_node(link_type p){ return list_node_allocator::deallocate(p); }//释放一个节点link_type create_node(const T& x){    //申请一个带值的节点link_type p = get_node();construct(&p->data,x);    //构造函数return p;
}link_type destroy_node(link_type p){  //销毁一个带值的节点destroy(&p->data);put_node(p);
}list() { empty_initialize(); }    //list构造函数,用于产生一个空链表void empty_initialize(){    //产生一个空节点node = get_node();node->next = node;noed->prev = node;
}

list的元素操作

        形如vector,list也提供了许多元素操作的函数,如insert(),push_back(),erase()和uniques()函数等操作,本小节将对这些元素操作进行源码的讲解,如下:

        1.insert()函数实现源码

//inser()用于在指定位置前插入元素
iterator inser(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))->next = tmp;position.node->prev = tmp;return tmp;
}

        2.push_front()函数实现源码

//push_front()用于插入一个节点作为头节点
void push_front(const T& x){insert(begin(),x);
}

        3.push_back()函数实现源码

//push_back()函数用于插入一个节点作为尾节点
void push_back(const T& x){insert(end(),x);
}

        4.erase()函数实现源码

//erase()函数用于移除指定节点
iterator erase(iterator position){link_type next_node = link_type(position.node->next);    //获取头指针指向的节点link_type prev_node = link_type(position.node->prev);    //获取尾指针指向的节点//更新移除节点的前后节点指针的指向prev_node->next = next_node;next_node->prev = prev_node;destory_node(position.node);   //释放当前节点return iterator(next_node);    //返回移除节点的上一个节点指针
}

        5.pop_front()函数实现源码

//pop_front()函数用于移除头节点
void pop_front(){rease(begin());
}

        6.pop_back()函数实现源码

//pop_back()函数用于移除尾节点
void pop_back(){iterator tmp = end();rease(--tmp);
}//因为真实的尾节点其值为空,参考环状双向链表结构体
//所以先获取尾节点后,再移动指针指向带有值的最后一个节点,最后释放该节点

        7.clear()函数实现源码

//clear()函数用于清除所有节点
template<class T , class Alloc>
void list<T,Alloc>::clear(){link_type cur = (link_type) node->next;    //获取链表头节点while(cur != node){    //遍历链表中的节点link_type tmp = cur;        cur = (link_type) cur->next;//更新为下一个节点destroy_node(tmp);}//恢复node状态node->next = node;node->prev = node;
}

        8.remove()函数实现源码

//remove()函数用于移除指定值的所有节点
template<class T, class Alloc>
void list<T,Alloc>::remove(const T& value){iterator first = begin();    //获取头节点iterator lase = end();       //获取尾节点while(first != lase){    //遍历所有节点iterator next = first;++next;    //获取下一个节点if(*first == value) erase(first);first = next;}
}

        9.unique()函数实现源码

//unique()函数用于移除数值相同的连续元素(最后剩余一个)
template<class T, class Alloc>
void list<T,Alloc>::unique(){iterator first = begin();iterator lase = end();if(first == last) teturn;    //空链表则退出iterator next = first;while(++next != last){    //遍历所有节点if(*first == *next)    erase(next);elsefirst = next;next = first;}
}

        10.transfer()函数实现源码

//transfer()函数用于将指定范围内的节点移动到指定节点的前面
void transfer(iterator position, iterator first, iterator last){//position:指定节点    first:范围的头节点    last:范围的尾节点(不包含至移动的范围中)if(position != last){(*(link_type((*last.node).prev))).next = position.node;(*(link_type((*first.node).prev))).next = last.node;(*(link_type((*position.node).prev))).next = first.node;link_type tmp = link_type((*position.node).prev);(*position.node).prev = (*last.node).prev;(*last.node).prev = (*first.node).prev;(*first.node).prev = tmp;}
}

        11.splice()函数实现源码

//splice()函数用于将指定的两个链表结合
void splice(iterator position, list& x){if(!x.empty()){    //判断链表是否为空transfer(position,x.begin(),x.end());}
}void splice(iterator position, list& x, iterator i){iterator j = i;++j;if(position == i || position == j) return;    //当前链表只存在一个节点transfer(position,i,j);
}void splice(terator position, list& x, iterator first, iterator last){if(first != last)  transfer(position,first,last)
}

        11.merge()函数实现源码

//merge()函数用于合并两个递增的链表,最后链表元素排序也为递增
template<class T, class Alloc>
void list<T,Alloc>::merge(list<T,Alloc>& x){iterator first1 = begin();iterator last1 = end();iterator first2 = x.begin();iterator last2 = x.end();while(first1 != last1 && first2 != last2){    //遍历次数为最短的链表元素个数if(*first2 < *first1){    //当链表1的值大于链表2iterator next = first2;transfer(first1,first2,++next);first2 = next;}else{++first1;}if(first2 != last2) { transfer(last,first1,first2); }    //如果链表没有插入完,则合并}
}

        12.reverse()函数实现源码

//reverse()函数用于见元素逆向排序
template<class T, class Alloc>
void list<T,Alloc>::reversr(){if(node->next == node || link_type(node->next)->next == node) return; //链表节点为1或0iterator first = begin();++first;while(first != end()){    遍历整个链表iterator old = first;++first;transfer(begin(),old,first);    //把头节点到old节点转移到链表前}
}

        13.sort()函数实现源码

//sort()函数用于对链表进行排序
template <class T, class Alloc>
void list<T, Alloc>::sort() {if(node->next == node || link_type(node->next)->next == node) return;//链表节点个数为0或1list<T, Alloc> carry;        //临时存储链表节点list<T, Alloc> counter[64];int fill = 0;while (!empty()) {carry.splice(carry.begin(), *this, begin());int i = 0;while(i < fill && !counter[i].empty()) {counter[i].merge(carry);carry.swap(counter[i++]);}carry.swap(counter[i]);         if (i == fill) ++fill;} for (int i = 1; i < fill; ++i) counter[i].merge(counter[i-1]);swap(counter[fill-1]);
}

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

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

相关文章

[9] CUDA性能测量与错误处理

CUDA性能测量与错误处理 讨论如何通过CUDA事件来测量它的性能如何通过CUDA代码进行调试 1.测量CUDA程序的性能 1.1 CUDA事件 CPU端的计时器可能无法给出正确的内核执行时间CUDA事件等于是在你的CUDA应用运行的特定时刻被记录的时间戳&#xff0c;通过使用CUDA事件API&#…

UVa1466/LA4849 String Phone

UVa1466/LA4849 String Phone 题目链接题意分析AC 代码 题目链接 本题是2010年icpc亚洲区域赛大田赛区的G题 题意 平面网格上有n&#xff08;n≤3000&#xff09;个单元格&#xff0c;各代表一个重要的建筑物。为了保证建筑物的安全&#xff0c;警察署给每个建筑物派了一名警察…

时代终结,微软宣布淘汰VBScript;Flink漏洞被广泛利用;Grandoreiro银行木马强势回归,1500多家银行成攻击目标 | 安全周报0524

揭秘SolarMarker恶意软件&#xff1a;多层次基础设施让清除工作陷入困境 Recorded Future的新发现表明&#xff0c;SolarMarker信息窃取恶意软件背后的持续威胁行为者已经建立了一个多层次的基础设施&#xff0c;以使执法部门的清除工作变得复杂。 该公司在上周发布的一份报告…

SwiftUI中AppStorage的介绍使用

在Swift中&#xff0c;AppStorage是SwiftUI中引入的一个属性包装器&#xff0c;在这之前我们要存储一些轻量级的数据采用UserDefaults进行存取。而AppStorage用于从UserDefaults中读取值&#xff0c;当值改变时&#xff0c;它会自动重新调用视图的body属性。也就是说&#xff0…

React@16.x(11)ref

目录 1&#xff0c;介绍1.1&#xff0c;得到的结果 2&#xff0c;参数类型2.1&#xff0c;字符串&#xff08;不再推荐&#xff09;2.2&#xff0c;对象2.3&#xff0c;函数函数调用时机 3&#xff0c;注意点 1&#xff0c;介绍 reference 引用。和 vue 中的 refs 类似&#x…

IEC60870-5-104通信规约 | 报文解析 | 组织报文与解析报文(C++)

文章目录 一、IEC60870-5-104通信规约1.IEC104的报文结构2.IEC104的报文格式--I/U/S格式2.1 I帧2.2 U帧2.3 S帧 3.应用服务数据单元ASDU 二、IEC60870-5-104规约通信过程报文帧解析三、组织报文与解析报文&#xff08;C&#xff09; 一、IEC60870-5-104通信规约 IEC60870-5-104…

什么是GPT-4o,推荐GPT-4o的获取使用方法,使用GPT4o模型的最新方法教程(2024年5月16更新)

2024年5月最新GPT-4o模型使用教程和简介 2024年5月最新GPT-4o模型使用教程和简介 2024 年 5 月 13 日&#xff0c;openai 发布了最新的模型 GPT4o。 很多同学还不知道如何访问GPT-4、GPT-4 Turbo和GPT-4o等模型&#xff0c;这篇文章介绍如何在ChatGPT中访问GPT-4o&#xff0…

无人机侦察:雷达系统概述

一、雷达基本原理 无人机侦察中的雷达系统主要基于无线电波的传播和反射原理。雷达发射机产生特定频率的电磁波&#xff0c;并通过天线以定向波束形式向空间发射。当这些电磁波遇到目标时&#xff0c;部分能量会被反射回来&#xff0c;被雷达接收机捕获。通过测量发射和接收电…

基于SpringBoot+Vue+Redis+Mybatis的商城购物系统 【系统实现+系统源码+答辩PPT】

前言 该系统采用SpringBootVue前后端分离开发&#xff0c;前端是一个单独的项目&#xff0c;后端是一个单独的项目。   技术栈&#xff1a;SpringBootVueMybatisRedisMysql   开发工具&#xff1a;IDEA、Vscode   浏览器&#xff1a;Chrome   开发环境&#xff1a;JDK1…

Pytorch 笔记

执行下面这段代码后&#xff0c;为什么返回的是 2 &#xff1f; vector torch.tensor([7, 7]) vector.shape为什么返回的是 torch.Size([2])&#xff1f; 当你创建一个PyTorch张量时&#xff0c;它会记住张量中元素的数量和每个维度的大小。在你的代码中&#xff0c;torch.t…

通过 js 调起微信官方的微信支付api

通过 js 调起微信官方的微信支付api function onBridgeReady() {WeixinJSBridge.invoke(getBrandWCPayRequest, { "appId": "wx2421b1c4370ec43b", // 公众号ID&#xff0c;由商户传入 "timeStamp": "1395712654", // 时间戳&quo…

使用canarytokens进行入侵检测

canarytokens 基本概念 canarytokens是一种用于识别网络入侵的工具。它们是一种虚拟的“蜜罐”&#xff0c;可以在网络上放置&#xff0c;当有人尝试访问它们时&#xff0c;可以立即触发警报&#xff0c;以便及时发现潜在的安全威胁。这些token可以是各种形式&#xff0c;可以…

项目管理基础知识

项目管理基础知识 导航 文章目录 项目管理基础知识导航一、项目相关概念二、时间管理三、人员管理四、风险管理 一、项目相关概念 项目定义的三层意思 一定的资源约束:时间资源、经费资源、人力资源一定的目标一次性任务 里程碑 是项目中的重要时点或事件持续时间为零&…

深度神经网络——什么是迁移学习?

1.概述 在练习机器学习时&#xff0c;训练模型可能需要很长时间。从头开始创建模型架构、训练模型&#xff0c;然后调整模型需要大量的时间和精力。训练机器学习模型的一种更有效的方法是使用已经定义的架构&#xff0c;可能具有已经计算出的权重。这是背后的主要思想 迁移学习…

makefile一些特殊且常用的符号

$^&#xff1a;表示所有的依赖文件列表&#xff0c;多个文件以空格分隔。 $&#xff1a;表示目标文件的名称。 $<&#xff1a;表示第一个依赖文件的名称。 $*&#xff1a;表示目标文件的主文件名&#xff08;不包括扩展名&#xff09;。 $?&#xff1a;表示所有比目标文件更…

Linux shell命令

cat 文件名 查看文件内容&#xff0c; tac文件名 倒着显示。 more 文件名 显示内容 less文件名 和more的功能一样&#xff0c;按上下左右键&#xff0c;按Q键结束。 head文件名&#xff0c;只显示前10行内容。 ln是一个默认创建硬链接的命令 ln 文件名 ls -i文件名…

SpringBoot整合RabbitMQ的快速使用教程

目录 一、引入依赖 二、配置rabbitmq的连接信息等 1、生产者配置 2、消费者配置 三、设置消息转换器 四、生产者代码示例 1、配置交换机和队列信息 2、生产消息代码 五、消费者代码示例 1、消费层代码 2、业务层代码 在分布式系统中&#xff0c;消息队列是一种重要…

00Java准备工作

目录 JDK的安装目录 JAVA环境变量的配置 JAVA小知识 JDK的安装目录 目录名称说明bin该路径下存放了JDK的各种工具命令,javac和java就放在这个目录conf该路径下存放了JDK的相关配置文件include该路径下存放了一些平台特定的头文件jmods该路径下存放了JDK的各种模块legal该路…

简单随机数据算法

文章目录 一&#xff0c;需求概述二&#xff0c;实现代码三、测试代码四、测试结果五、源码传送六、效果演示 一&#xff0c;需求概述 系统启动时&#xff0c;读取一组图片数据&#xff0c;通过接口返回给前台&#xff0c;要求&#xff1a; 图片随机相邻图片不重复 二&#…

进程互斥经典问题(读写者问题、理发店问题)

目录 读写者问题 问题描述 问题分析 进程互斥问题三部曲 读者写者算法实现 一、找进程——确定进程关系 二、找主营业务 三、找同步约束 a.互斥 b.资源 c.配额 理发店问题 问题描述 问题分析 进程互斥问题三部曲 理发店问题算法实现 一、找进程——确定进程…