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

news2024/11/19 3:17:56

目录

        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);
        else
            first = 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的值大于链表2
            iterator 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或0
    iterator 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或1

  list<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.coloradmin.cn/o/1710346.html

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

相关文章

哈希冲突的常见解决方法【附C++代码】

在C中&#xff0c;哈希表是一种常用的数据结构&#xff0c;用于实现快速的插入、删除和查找操作。 哈希表的核心在于哈希函数&#xff0c;它将输入的关键字转换为一个数组索引。然而&#xff0c;不同的关键字可能映射到相同的索引&#xff0c;这种情况称为哈希冲突。 有效地解…

【MIT 6.5840(6.824)学习笔记】GFS

1 分布式存储系统难点 在设计大型分布式系统或存储系统时&#xff0c;初衷通常是为了获得显著的性能提升&#xff0c;通过数百台计算机的资源来并行完成大量工作。因此&#xff0c;性能问题成为最初的关注点。一个自然的想法是将数据分片&#xff08;Sharding&#xff09;&…

GPT-4 与 GPT-4 Turbo有什么区别?

在不断发展的人工智能和自然语言处理领域&#xff0c;OpenAI 的 GPT 系列一直走在最前沿&#xff0c;彻底改变了机器理解和生成类人文本的方式。每一次迭代&#xff0c;进步都会突破可能性的界限。 最新的条目 GPT-4 和 GPT-4 Turbo 引起了人工智能社区内外的极大兴趣和争论。…

保留字与标识符

目录 保留字 标识符 自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 保留字 保留字是Python语言中一些已经被赋予特定意义的单词。开发程序时&#xff0c;不可以把这些保留字作为变量、函数、类、…

河道流量监测解决方案 河道水位监测 水质在线监测-计讯物联科技

在城市河道与入海口间的通道施工项目中&#xff0c;拦水坝不仅承担着调节水流、保护生态平衡的使命&#xff0c;也是确保施工区域安全的关键屏障。当前&#xff0c;项目团队面临着严峻的挑战&#xff1a;水位的异常上升和流量的急剧变化&#xff0c;这些都可能对拦水坝的稳定性…

公司预防文件泄密的常见手段 | 文件防泄密软件推荐排行榜

在当今信息化社会&#xff0c;企业面临着越来越多的文件泄密风险。为了保护企业的核心信息和资产&#xff0c;公司需要采取一系列手段来预防文件泄密。本文将介绍公司预防文件泄密的常见手段&#xff0c;并推荐五款优秀的防泄密软件&#xff0c;帮助企业构建更为严密的数据安全…

Mongodb 可视化工具Robot 3t安装【windows环境下】

下载应用 打开连接点我 选择windows版本并点击下载 下载完毕&#xff0c;双击并傻瓜安装 连接数据库 点击图标&#xff0c; 点击create创建连接 填写host和port 如果有用户名密码的&#xff0c;在authentication里填写 5. save 并连接即可使用&#xff01;

Python自动化办公Excel数据处理实战指南

目录 一、引言 二、需求分析 三、技术选型 四、实战操作 数据读取 数据清洗 数据分析 数据输出 五、学习资源推荐&#xff1a; 六、结语 一、引言 在现代办公环境中&#xff0c;Excel数据处理是一项不可或缺的技能。然而&#xff0c;当数据量庞大、处理流程复杂时&a…

页面加载不出来,报错[@umijs/runtime] load component failed

问题描述 页面加载不出来数据&#xff0c;一直在旋转&#xff0c;控制台输出内容如下&#xff1a; 原因分析&#xff1a; 之前页面是没有问题的&#xff0c;在写当前页面突然出现页面加载不出来&#xff0c;控制台报错&#xff0c;主要是页面引入了这行代码报错 import { …

教育小程序的性能优化:从前端到后端的综合提升策略

随着教育小程序的普及&#xff0c;其性能直接影响用户体验和教学效果。本文将从前端到后端&#xff0c;详细探讨教育小程序的性能优化策略&#xff0c;帮助开发者打造高效、流畅的教育应用。 一、前端性能优化策略 代码优化 减少HTTP请求&#xff1a;合并CSS、JavaScript文件…

安装CUDA Toolkit解决异常:OSError: CUDA_HOME environment variable is not set.

安装CUDA Toolkit 安装CUDA Toolkit异常信息分析下载CUDA执行安装配置环境变量验证 安装CUDA Toolkit 异常信息 在执行pip install flash_attn&#xff0c;安装一个推理加速库的时候&#xff0c;遇到如下异常&#xff1a; Looking in indexes: https://mirrors.aliyun.com/p…

RocketMQ学习(1) 快速入门

mq的一些前置知识和概念知识可以看这篇文章——SpringCloud入门(3) RabbitMQ&#xff0c;比如常见mq的对比等等&#xff0c;这篇文章不再赘述。 目录 RocketMQ概念、安装与配置docker配置 RocketMQ快速入门**同步消息消费模式 **异步消息*单向消息**延迟消息*顺序消息批量消息事…

探索Python函数参数的奥秘

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、揭开函数参数的神秘面纱 1. 位置参数&#xff1a;按序传值的基石 2. 关键字参数&#…

修改Windows系统hosts文件,解决GitHub国内访问速度慢甚至无法访问的问题

对国内大多数用户&#xff0c;GitHub的访问速度非常慢&#xff0c;甚至是打不开&#xff0c;无法访问。究其原因&#xff0c;多数是GitHub的CDN域名解析&#xff08;DNS&#xff09;遭到了污染或拦截。本文以Windows 10系统为例&#xff0c;通过修改本地hosts文件&#xff0c;解…

基于jeecgboot-vue3的Flowable流程-我的任务(一)

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、首先可以用现成生成代码的前端来做这个&#xff0c;只要做一些调整就可以了&#xff0c;这样利用现有的一些模板可以快速构建我的任务&#xff0c;否则vue2与vue3相差太大&#xff0c;移…

Linux虚拟主机中如何创建文件和文件夹

我想创建一个新的文件夹&#xff0c;由于我使用的Hostease的Linux虚拟主机产品默认带普通用户权限的cPanel面板&#xff0c;但是不知道如何在cPanel上操作创建文件&#xff0c;因为也是对于Hostease主机产品不是很了解&#xff0c;因此联系Hostease的咨询了Hostease技术支持&am…

C++基础练手项目之贪吃蛇

同理上一篇写的 http://t.csdnimg.cn/TPFwu 就是画一个地图,用二维数组,来记录X和Y轴,就可以定义到这个地图的任意位置. 目录 一.步骤解析 二.代码 三.扩展 一.步骤解析 开始函数,变量值重置为默认状态,蛇的头,尾生成位置都可以,随便更改,我这里先默认写死了, 循环等待输入…

移除重复节点

题目链接 移除重复节点 题目描述 注意点 链表未排序链表长度在[0, 20000]范围内链表元素在[0, 20000]范围内 解答思路 使用Set存储访问过的链表中出现的节点值&#xff0c;当遍历到链表的某个节点在Set中出现过&#xff0c;则需要将该节点的前一个节点next指针指向该节点的…

护眼灯到底有用吗?引发护眼台灯危害的四大原因曝光!

护眼灯到底有用吗&#xff1f;近几年随着各大科技感满满的设备诞生&#xff0c;近视率也伴随着不断提高&#xff0c;现如今是已经攀升到了惊人的53.6%&#xff0c;这一数据也清晰的警惕着每一位家长&#xff0c;此刻护眼灯以独特的护眼效果脱颖而出&#xff0c;同时也在书房中占…

Linux - crond任务调度、at定时任务

1 crontab 进行-定时任务的设置 1&#xff09;概述&#xff1a; 任务调度&#xff1a;是指系统在某个时间执行的特定的命令或程序。 任务调度分类&#xff1a; 系统工作&#xff1a;有些重要的工作必须周而复始地执行。如病毒扫描等个别用户工作&#xff1a;个别用户可能希…