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);
        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]);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

集合的交集、并集和差集运算

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 集合最常用的操作就是进行交集、并集、差集和对称差集运算。进行交集运算时使用“&”符号&#xff0c;进行并集运算时使用“&#xff5c;”符号&…

SQL数据库多层嵌套 json转sql建表语句,SQL数据库里数组里对象数据怎么创建

1. uniapp sqlite 一个数组包含对象嵌套对象通过主外键方式插入数据库&#xff1a; // 假设有一个对象数组&#xff0c;对象中包含嵌套对象 const objectsArray [{parentObject: {id: 1,name: Parent 1,// 其他父对象属性},childObject: {id: 11,parentId: 1,name: Child 1 o…

vue中的$nextTick和过渡与动画

一.vue中的$nextTick 简述与用法&#xff1a;这是一个生命周期钩子 1.语法&#xff1a;this.$nextTick(回调函数) 2.作用&#xff1a;在下一次DOM更新结束后执行其指定的回调 3.什么时候用&#xff1a;当修改数据后&#xff0c;要基于更新后的新dom进行某些操作时&#xff0c;…

精酿啤酒:品质与口感在不同消费人群中的差异与共性

在啤酒市场中&#xff0c;不同消费人群对品质与口感的喜好存在一定的差异。然而&#xff0c;Fendi club啤酒凭借其卓着的品质和与众不同的口感&#xff0c;在不同消费人群中都展现出一定的共性。 从性别差异来看&#xff0c;男性消费者通常更注重啤酒的品质和口感&#xff0c;而…

Llama 3 模型家族构建安全可信赖企业级AI应用之使用 Llama Guard 保护大模型对话 (八)

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…

机器学习(七) ----------聚类(K-means)

目录 1 核心思想 2 K-means算法 2.1 算法概述 2.2 算法步骤 2.3 数学原理 2.4 ‘肘’方法确定K值 2.4.1 原理 2.4.2 步骤 2.4.3 代码实现 2.5 聚类评估方法 2.5.1 SC轮廓系数&#xff08;Silhouette Coefficient&#xff09; 计算方法 解读 注意事项 2.5.2 Cal…

Windows UWP ContentDialog去掉阴影(全透明)的实现

一、前言 在WIndows开发中&#xff0c;使用UWP&#xff08;Universal WIndows&#xff09;项目开发过程中&#xff0c;使用ContentDialog 的过程中&#xff0c;我们可能并不满足现有的样式&#xff0c;这时就需要自定义样式。笔者在自定义样式过程中&#xff0c;遇到了一个难题…

数据库多表查询

多表查询&#xff1a; SELECT *FROM stu_table,class WHERE stu_table.c_idclass.c_id; 多表查询——内连接 查询两张表交集部分。 隐式内连接&#xff1a; #查询学生姓名&#xff0c;和班级名称&#xff0c;隐式调用 SELECT stu_table.s_name,class.c_name FROM stu_table…

php反序列化学习(1)

1、php面向对象基本概念 类的定义&#xff1a; 类是定义了一件事物的抽象特征&#xff0c;它将数据的形式以及这些数据上的操作封装住在一起。&#xff08;对象是具有类类型的变量&#xff0c;是对类的实例&#xff09; 构成&#xff1a; 成员变量&#xff08;属性&#xf…

来自工业界的知识库 RAG 服务(二),RagFlow 源码全流程深度解析

背景介绍 前面介绍过 有道 QAnything 源码解析&#xff0c;通过深入了解工业界的知识库 RAG 服务&#xff0c;得到了不少调优 RAG 服务的新想法。 因此本次趁热打铁&#xff0c;额外花费一点时间&#xff0c;深入研究了另一个火热的开源 RAG 服务 RagFlow 的完整实现流程&…

上交提出TrustGAIN,提出6G网络中可信AIGC新模式!

月16日至18日&#xff0c;2024全球6G技术大会在南京召开。会上&#xff0c;全球移动通信标准制定组织3GPP&#xff08;第三代合作伙伴计划&#xff09;的3位联席主席分享了3GPP6G标准时间表&#xff1a; 2024年9月&#xff0c;启动6G业务需求研究&#xff1b; 2025年6月&…

FastReport 主子表关系

代码中只需要绑定主表的数据就可以&#xff0c;子表的数据会通过报表中的关连关系自动到数据库中带出。 using CloudSaaS.DB.Handler; using CloudSaaS.Model; using CloudSaaS.DAL; using FastReport; using FastReport.Web; using System; using System.Collections.Generic;…

Hotcoin Research | 市场洞察:2024年5月13日-5月19日

加密货币市场表现 目前&#xff0c;加密货币总市值为1.32万亿&#xff0c;BTC占比54.41%。 本周行情呈现震荡上行的态势&#xff0c;BTC在5月15日-16日&#xff0c;有一波大的拉升&#xff0c;周末为震荡行情。BTC现价为67125美元。 上涨的主要原因&#xff1a;美国4月CPI为3…

Oracle创建用户时提示ORA-65096:公用用户名或角色名无效

Oracle创建用户时提示“ORA-65096&#xff1a;公用用户名或角色名无效” 如下图所示&#xff1a; 解决方法&#xff1a;在新增用户名前面加上C##或者c##就可以解决无效问题&#xff0c;具体什么原因还不清楚&#xff0c;需要再研究一下。

JS 中怎么删除数组元素?有哪几种方法?

正文开始之前推荐一位宝藏博主免费分享的学习教程,学起来! 编号学习链接1Cesium: 保姆级教程+源码示例2openlayers: 保姆级教程+源码示例3Leaflet: 保姆级教程+源码示例4MapboxGL: 保姆级教程+源码示例splice() JavaScript中的splice()方法是一个内置的数组对象函数, 用于…

vr数字成果展在线展示突破用户传统认知

想要轻松搭建一个充满互动与创意的3D数字展厅吗?vr互动数字展厅搭建编辑器将是您的不二之选!华锐视点3D云展平台提供的vr互动数字展厅搭建编辑器将空间重建与互动制作完美结合&#xff0c;让您轻松实现3D空间的搭建与互动营销制作。 在vr互动数字展厅搭建编辑器的帮助下&#…

SpringBoot 返回值 i18n 自动处理

定义基础通用类 首先定义一波错误码&#xff1a;ResultCode Getter AllArgsConstructor public enum ResultCode {SUCCESS(200, "请求成功", "request.success"),Fail(400, "请求失败", "request.failed"),PASSWORD_NOT_MATCH(1000…

独家揭秘!Amazon、lazada、Shopee测评自养号,新手也能秒变高手!

近年来&#xff0c;随着国内卖家涌入跨境电商平台&#xff0c;市场竞争愈加激烈。为了迅速占领市场&#xff0c;测评变得至关重要。然而&#xff0c;真人测评供不应求&#xff0c;服务商账号质量不一&#xff0c;且存在高权重账号稀缺和黑卡下单风险。因此&#xff0c;许多大卖…

为什么选择CleanMyMac软件呢?推荐理由

你是否曾经遇到过这样的问题&#xff1a;电脑运行缓慢&#xff0c;存储空间不足&#xff0c;不知道如何清理垃圾文件&#xff1f;别担心&#xff0c;我们为你找到了解决方案——CleanMyMac软件。这款强大的工具可以帮助你轻松解决这些问题&#xff0c;让你的电脑焕然一新&#…

VirtualBox+Ubuntu22.10+Docker+ROS2

Docker 拉取ros2镜像 docker pull osrf/ros:foxy-desktop 运行 docker run -it --nameros2 -p 50022:22 osrf/ros:foxy-desktop 进入容器安装组件 apt-get update apt-get install vim apt-get install git apt-get install net-tools # 安装ssh apt-get install openssh…