C++基础语法:STL之容器(4)--序列容器中的list(一)

news2024/12/23 10:03:54

前言

       "打牢基础,万事不愁" .C++的基础语法的学习

引入


        序列容器的学习.以<C++ Prime Plus> 6th Edition(以下称"本书")内容理解

        本书中容器内容不多只有几页.最好是有数据结构方面的知识积累,如果没有在学的同时补上.

        序列容器回顾:序列容器内元素按严格线性顺序排列,至少是正向迭代器(含以上).序列容器包括deque(双端队列),forward_list(单链表),list(双向链表),queue(队列),priority_queue(优先队列),stack(栈),vector(动态数组),array(替代数组的容器

list(双向链表) 

        list所占篇幅相对其他容器类算比较大的,而且有专属的api介绍.

        list双向链表,和单链表比较起来,在结点上多了个指向前面一个元素的指针.

        本书内容解读 

        第1部分:  list模板类(在list头文件中声明)表示双向链表。除了第一个和最后一个元素外,每个元素都与前后的元素相链接,这意味着可以双向遍历链表。list和vector之间关键的区别在于,list在链表中任一位置进行插入和删除的时间都是固定的(vector模板提供了除结尾处外的线性时间的插入和删除,在结尾处,它提供了固定时间的插入和删除)。因此,vector强调的是通过随机访问进行快速访问,而list强调的是元素的快速插入和删除 (本书原话)

         ----蓝色部分是list应用场景,切记

         ----代码和解读:

        注意:下列代码为了练手,试图重现逻辑,不保证准确.          

template<class T>
class list{
    enum{MAX=10}
    int lsize;                //list最大元素数量
    int items;                //list内当前的元素个数
    class Node{               //声明结点类
    public:                   //结点数据向外部类公开
        T t;
        Node *front;
        Node *next;
        Node(T val):t(val),front(0),next(0){}
        Node(){}              //默认构造函数,为初始化时使用
    }
    Node* first;
    Node* last;
public:
    list(int num=MAX);        //构造函数   
    void add(Node* n,T& t);   //添加元素t到结点n后面
    T remove(Node* n);        //删除地址为n的结点
}

        1>构造函数,建立初始的list

        说明:first按照"头结点"定义,数据域为空的结点,初始化时没有元素,所以last也指向头结点.

template<class T>
list::list(int num=MAX):lsize(num){ //初始化list,没有元素时的情况
    items=0;                        //初始时元素个数为0
    Node *newNode=new Node;         //创建数据域为空的结点
    first=newNode;                  //头结点指向空结点;
    last=newNode;                   //末结点指向空结点;

//  last->next=first;               //如果加上这句,末结点后面的结点指向头结点,形成环状list
                                    //这里不加,仍然是一根链条似的list,加了变复杂用处也不大,不加
}

        2>添加元素

        把结点地址作为参数,作为插入元素的条件,向序列要求函数中的迭代器靠拢.

template<class T>
void list<T>::add(Node* n,T& t){
    Node* newNode=new Node(t);    //生成新结点,传入数据
/*新结点后面是谁*/
    newNode->next=n->next;
    n->next->front=newNode;
/*新结点在谁后面*/
    n->next=newNode;
    newNode->front=n;
/*第一次插入后,新结点成为尾结点,并在头结点后面*/
    if(items==0){                //第一次插入时情况:区分first和last
        newNode->next=nullptr;   //新结点后面指空
        last=newNode;            //新结点成了尾结点
        first->next=newNode;     //first当上了头结点
        newNode->front=first;    //两个方向说明first当上了头结点
    }            
//  if(n==first)
//      first->next=newNode;     //插入在头结点之后,前面代码已符合不用重复
    if(n==last)                 
        last=newNode;            //如果在末尾插入,尾结点指向新结点仍是尾结点
    items++;                     //list元素个数加1
}

        3>删除某个位置的结点 

template<class T>
T list<T>::remove(Node* n){
  Node* tmp=n;                //标识要删除的结点
  /*把要删除的结点从list里面剥离出来*/
  n->next->front=n->front;    //该结点后面结点的front指向该结点前面那个结点
  n->front->next=n->next;     //该结点前面那个结点的next指向该结点后面

  if(n==last)                 //如果删除的是尾结点
       last=n->front;         //尾结点指向删除结点的前一个结点
  T t=tmp->t;                 //标识结点的数据取出来
  delete tmp;                 //删除标识结点
  items--;                    //删除结点,元素个数减1
  return t;                   //返回原结点内数据
}        

================================内容分割线=================================

做几个小分析:

        1.构造函数用了两个,如果只有下面这个,建立空结点不知道能不能成功,笔者未尝试.

          在C语言中,声明一个结构体并且malloc,好像不用给数据也没错,C++的检查更严格.

Node(T val):t(val),front(0),next(0){}

        2.关于环状list

          如果采用"环状"list,那么后面的代码中last不能指空,而要指向first.其他算法可能会有区别

           在插入,删除或者查找中,环状list未必能达到好的效果.考虑到一种场景:list很长,查询数据时查一半不找了,往回查找走,这时考虑用环状list.如图:

     有兴趣可以尝试做个环状list,再写个查找算法.

     不过数据多了有更好的选择,比如二叉树等,所以感觉实用性不大.

        3. 头结点  

        头结点实际上是"人造"的.他的用途是方便元素在头部插入和删除.是否选择用头结点在于程序员.如果不做头结点,让头部插入的结点成为首个结点,那么代码要做一些修改,代码要多一点.

        4.第一次插入时的描述

        以下是函数add里的部分代码:

/*第一次插入后,新结点成为尾结点,并在头结点后面*/
    if(items==0){                //第一次插入时情况:区分first和last
        newNode->next=nullptr;   //新结点后面指空
        last=newNode;            //新结点成了尾结点
        first->next=newNode;     //first当上了头结点
        newNode->front=first;    //两个方向说明first当上了头结点
    }

        参照本书P614,链队列的"入队"算法enque,开始时first和last都指向同一个空结点,将第一次插入和其他次插入分开,才可以在逻辑上区分first和last,保证后面的程序正确. 

        5.程序中的"一般"和"特别"

        add中的部分代码 

//  if(n==first)
//      first->next=newNode;     //插入在头结点之后,前面代码已符合不用重复
    if(n==last)                 
        last=newNode;            //如果在末尾插入,尾结点指向新结点仍是尾结点

        当写完add后,如果想在头结点后插入元素,代入first,发现逻辑仍成立,所以注释部分属于多余描述;而当在末尾结点插入元素,代入last,函数执行完毕后发现尾结点位置没变,所以给了if做补充. 

        函数代入的形参可以被看作是所有可能的组合 ,表示"一般"性.当一般性不能满足所有情况,需要用"特殊"的描述做补充,这也是程序调试的重要性所在.

        6.尾结点last为什么有时候需要有时候不需要?

        对比以前的数据结构单链表C++基础语法:链表和数据结构-CSDN博客和链队列,他们一个没有尾结点,一个有尾结点,list也有尾结点.而链队列和list的共同特征是需要在"尾部"插入和删除元素,因此定义了尾结点last并实现了他.而使用"头插法"的单链表既没有"尾部"的概念,也没有"尾结点"存在.与此相对应的,头结点(指向首个元素的结点)是必须存在的,因为靠他遍历到容器内所有数据.

        同时定义了list的最大元素个数items,但并没有使用他,所以本例的链表可以无限长 

        结论:容器里的属性是根据需要定义并实现的

        7.迭代器

        此前迭代器让人挠头,迭代器类里的属性复刻了容器里的数据(因为容器里都是数据集合,所以属性是容器集合的指针),所以迭代器实际上是对数据的二重访问和修改.提升了"同一性"(每个容器里都有个迭代器类).在容器类里对元素的增删改搬到迭代器里去了,然后做接口被容器类对象访问.

        迭代器做参数,先转化成对应的指针即可.本例的指针做参数和迭代器做参数已非常接近 

        8.函数的"冗余"

        在序列函数中,有push_front()函数,为了在容器头部插入数据,有了add()函数也一样可以实现.为什么要这样做呢?

        原因和"迭代器"一样,他是为了同属于序列容器"同一性"提供的api,而且也容易实现,把参数传给add()就行了. 

================================内容分割线================================ 

         第2部分:与vector相似,list也是可反转容器。与vector不同的是,list不支持数组表示法和随机访问。与矢量迭代器不同,从容器中插入或删除元素之后,链表迭代器指向元素将不变。我们来解释一下这句话。例如,假设有一个指向vector容器第5个元素的迭代器,并在容器的起始处插入一 个元素。此时,必须移动其他所有元素,以便腾出位置,因此插入后,第5个元素包含的值将是以前第4个元素的值。因此,迭代器指向的位置不变,但数据不同。然后,在链表中插入新元素并不会移动已有的元素,而只是修改链接信息。指向某个元素的迭代器仍然指向该元素,但它链接的元素可能与以前不同。

        ----解读:这段比较容易理解:如果支持随机访问,那么两次访问到的数据不能改变.而list(包括其他链表)在插入和删除后,原数据的位置发生改变,再次用位置访问到的数据和之前不一样了,所以不能随机查找,而只能通过遍历来搜寻.

小结

        list双向链表的一些理解.

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

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

相关文章

加密软件有什么用?五款电脑文件加密软件推荐

加密软件对于个人和企业来说至关重要&#xff0c;尤其是在2024年这样一个高度数字化的时代&#xff0c;数据安全变得尤为重要。 数据保护&#xff1a;加密软件可以保护敏感信息不被未经授权的人访问。这包括个人数据、财务记录、健康信息、企业机密等。 防泄漏&#xff1a;防…

组合数学+费用背包+刷表,G2 - Playlist for Polycarp (hard version)

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 G2 - Playlist for Polycarp (hard version) 二、解题报告 1、思路分析 一…

Linux驱动开发-05APP和驱动的交互方式

一、传输数据 APP和驱动: copy_to_usercopy_from_user驱动和硬件: 各个子系统的函数通过ioremap映射寄存器地址后,直接访问寄存器二、APP使用驱动的四种方式 驱动程序:提供能力,不提供策略 非阻塞(查询)(应用程序访问底层驱动时(read、write时),驱动没有数据不等待,…

【Vue】深入理解 `v-model` 指令:实现数据双向绑定的终极指南

文章目录 一、v-model 指令概述二、v-model 的基本用法1. 绑定输入框的值2. 绑定多行文本框的值3. 绑定复选框4. 绑定单选框5. 绑定选择框 三、v-model 的工作原理四、v-model 的高级用法1. 自定义组件中的 v-model模板部分 (<template>)脚本部分 (<script>) 2. 多…

vue项目源码调试方法 ,chrome调试控制台工作区使用,利用chrome控制台调试vue项目源码的方法 图解

我们在开发vue项目的时候&#xff0c;项目开始后的第一件事情应该就是准备调试工作了&#xff0c;Chrome调试控制台就给我们提供了这样的一个方便的调试工作区。方法为&#xff0c;在Chrome浏览器里面打开你要调试的页面&#xff0c;然后打开调试控制台&#xff0c;如下&#x…

基于STC8H4K64TL单片机的触摸功能和数码管驱动功能实现一个触摸按键单击长按都增加数值另一个触摸按键单击长按都减少数值应用

基于STC8H4K64TL单片机的触摸功能和数码管驱动功能实现一个触摸按键单击长按都增加数值另一个触摸按键单击长按都减少数值应用 STC8H4K64TL单片机介绍STC8H4K64TL单片机管脚图(48个引脚)STC8H4K64TL单片机串口仿真与串口通信STC8H4K64TL单片机管脚图(32个引脚)STC8H4K64TL单…

【Linux】汇总TCP网络连接状态命令

输入命令&#xff1a; netstat -na | awk /^tcp/ {S[$NF]} END {for(a in S) print a, S[a]} 显示&#xff1a; 让我们逐步解析这个命令&#xff1a; netstat -na: netstat 是一个用于显示网络连接、路由表、接口统计等信息的命令。 -n 选项表示输出地址和端口以数字格式显示…

ACCL+: an FPGA-Based Collective Engine for Distributed Applications——论文泛读

OSDI 2024 Paper 论文阅读笔记整理 问题 FPGA在云部署中越来越普遍[16,81]&#xff0c;如智能NIC[29,35,64,67103]、流处理器[31,32,55,68]和分布式加速器[15,41,61,65,86,93115]。可以使用FPGA到FPGA的直接通信来构建高效的分布式系统。然而&#xff0c;使用FPGA设计分布式应…

通过 EMR Serverless Spark 提交 PySpark 流任务

在大数据快速发展的时代&#xff0c;流式处理技术对于实时数据分析至关重要。EMR Serverless Spark提供了一个强大而可扩展的平台&#xff0c;它不仅简化了实时数据处理流程&#xff0c;还免去了服务器管理的烦恼&#xff0c;提升了效率。本文将指导您使用EMR Serverless Spark…

python爬虫获取网易云音乐评论歌词以及歌曲地址

python爬虫获取网易云音乐评论歌词以及歌曲地址 一.寻找数据接口二.对负载分析三.寻找参数加密过程1.首先找到评论的请求包并找到发起程序2.寻找js加密的代码 四.扣取js的加密源码1.加密函数参数分析①.JSON.stringify(i0x)②bse6Y(["流泪", "强"])③bse6Y…

Linux--Socket套接字编程

Socket编程 Socket编程是一种在网络中不同计算机之间实现数据交换的编程方式。它允许程序创建网络连接&#xff0c;并通过这些连接来发送和接收数据。Socket编程是网络编程的基础&#xff0c;广泛应用于客户端-服务器&#xff08;C/S&#xff09;架构中。 要实现双方通信&…

【扩散模型(六)】Stable Diffusion 3 diffusers 源码详解1-推理代码-文本处理部分

系列文章目录 【扩散模型&#xff08;一&#xff09;】中介绍了 Stable Diffusion 可以被理解为重建分支&#xff08;reconstruction branch&#xff09;和条件分支&#xff08;condition branch&#xff09;【扩散模型&#xff08;二&#xff09;】IP-Adapter 从条件分支的视…

基于python的去除图像内部填充

1 代码功能 该代码实现了一个图像处理的功能&#xff0c;具体来说是去除图像内部填充&#xff08;或更准确地说&#xff0c;是提取并显示图像中轮廓的外围区域&#xff0c;而忽略内部填充&#xff09;。以下是该功能的详细步骤&#xff1a; 读取图像&#xff1a;使用cv2.imread…

Hadoop-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; HadoopHDFSMapReduceHiveFlumeSqoopZookeeperHBaseRedis 章节内容 上一节我们完成了&#xff1a; HBase …

机器学习-18-统计学与机器学习中回归的区别以及统计学基础知识

参考通透!一万字的统计学知识大梳理 参考3万字长文!手把手教你学会用Python实现统计学 参考统计学的回归和机器学习中的回归有什么差别? 1 研究对象 一维:就是当前摆在我们面前的“一组”,“一批数据。这里我们会用到统计学的知识去研究这类对象。 二维:就是研究某个“事…

【系统架构设计】数据库系统(三)

数据库系统&#xff08;三&#xff09; 数据库模式与范式数据库设计备份与恢复分布式数据库系统分布式数据库的概念特点分类目标 分布式数据库的架构分布式数据库系统与并行数据库系统 数据仓库数据挖掘NoSQL大数据 数据库模式与范式 数据库设计 备份与恢复 分布式数据库系统…

生活中生智慧

【 圣人多过 小人无过 】 觉得自己做得不够才能做得更好&#xff0c;互相成全&#xff1b;反求诸己是致良知的第一步&#xff1b;有苦难才能超越自己&#xff0c;开胸怀和智慧&#xff1b;不浪费任何一次困苦&#xff0c;危机中寻找智慧&#xff0c;成长自己。 把困苦当作当下…

自动驾驶三维车道线检测系列—LATR: 3D Lane Detection from Monocular Images with Transformer

文章目录 1. 概述2. 背景介绍3. 方法3.1 整体结构3.2 车道感知查询生成器3.3 动态3D地面位置嵌入3.4 预测头和损失 4. 实验评测4.1 数据集和评估指标4.2 实验设置4.3 主要结果 5. 讨论和总结 1. 概述 3D 车道线检测是自动驾驶中的一个基础但具有挑战性的任务。最近的进展主要依…

【NetTopologySuite类库】GeometryFixer几何自动修复,解决几何自相交等问题

介绍 NetTopologySuite 2.x 提供了GeometryFixer类&#xff0c;该类能够将几何体修复为有效几何体&#xff0c;同时尽可能保留输入的形状和位置。几何的IsValid属性&#xff0c;反映了几何是否是有效的。 输入的几何图形始终会被处理&#xff0c;因此即使是有效的输入也可能会…

特征工程方法总结

方法有以下这些 首先看数据有没有重复值、缺失值情况 离散&#xff1a;独热 连续变量&#xff1a;离散化&#xff08;也成为分箱&#xff09; 作用&#xff1a;1.消除异常值影响 2.引入非线性因素&#xff0c;提升模型表现能力 3.缺点是会损失一些信息 怎么分&#xff1a;…