当前位置: 首页 > news >正文

C++STL容器系列(三)list的详细用法和底层实现

目录

  • 一:介绍
  • 二:list的创建和方法
    • 创建list
    • 方法
  • 三:list的具体用法
    • 3.1 push_back、pop_back、push_front、pop_front
    • 3.2 insert() 和 erase()
    • 3.3 splice 函数
  • 四:list容器底层实现
    • 4.1 list 容器节点结构
    • 5.2 list容器迭代器的底层实现
    • 5.2 list容器的主体实现
  • 总结

对2024年航空航天与力学国际学术会议(ICAM 2024) 感兴趣的伙伴可以点击【click】

一:介绍

  • list是一种序列式容器。该容器的底层是用双向链表实现的, 在链表的任一位置进行元素的插入、删除操作都是快速的。

二:list的创建和方法

首先,使用vector时需包含头文件:

  • #include <list>

创建list

list本质是类模板,可以存储任何类型的数据。数组在声明前需要加上数据类型,而list则通过模板参量设定类型。

比如,声明一个int型的list列表。

list<A> listname;					// 一个空列表list<A> listname(size);				// 开辟size个空间,值默认为0list<A> listname(size, value);      // size个值为value的数组list<A> listname(elselist);         //  拷贝构造参数是其他listlist<A> listname(first, last);      // 迭代器初始化

方法

✨iterators(迭代器) \colorbox{pink}{✨iterators(迭代器)} ✨iterators(迭代器)

名字描述
begin返回指向容器中第一个元素的迭代器。
end返回指向容器最后一个元素所在位置后一个位置的迭代器
rbegin返回容器逆序的第一个元素的迭代器
rend返回容器逆序的最后一个元素的前一个位置的迭代器
cbegin和begin()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
cend和end()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
crbegin和rbegin()功能相同,在其基础上增加了 const 属性,不能用于修改元素。
crend和rend()功能相同,在其基础上增加了 const 属性,不能用于修改元素。

✨Capacity(容量) \colorbox{pink}{✨Capacity(容量)} ✨Capacity(容量)

名字描述
size返回实际元素的个数
empty判断list是否为空,为空返回true否则false
max_size返回元素个数的最大值。

✨Element access(元素访问) \colorbox{pink}{✨Element access(元素访问)} ✨Element access(元素访问)

名字描述
front返回第一个元素
back返回最后一个元素

✨Modifiers(修改器) \colorbox{pink}{✨Modifiers(修改器)} ✨Modifiers(修改器)

名字描述
push_back在容器的尾部插入元素
pop_back删除最后一个元素
push_front在容器的头部插入元素
pop_front删除第一个元素
erase删除元素
clear清除容器内容,size=0,存储空间不变
swap交换两个元素的所有内容
assign用新元素替换原有内容。
emplace_front插入元素,和insert实现原理不同,速度更快
emplace_back在容器的尾部插入元素,和push_back不同, 速度更快

三:list的具体用法

3.1 push_back、pop_back、push_front、pop_front

list<int> lt1;
for (int i = 0; i < 5; i++)
{lt1.push_back(i);lt1.push_front(i);
}
for (int i = 0; i < 5; i++)
{lt1.pop_back();lt1.pop_front();
}
  • emplace_back的效果和push_back一样,具体可以查看前一篇vector的博客

3.2 insert() 和 erase()

  • insert():在指定位置插入一个或多个元素(三个重载)
l1.insert(l1.begin(), 100);  在l1的开始位置插入100。l1.insert(l1.begin(), 2, 200);  在l1的开始位置插入2100。l1.insert(l1.begin(), l2.begin(), l2.end()); 在l1的开始位置插入l2的从开始到结束的所有位置的元素。
  • erase():删除一个元素或一个区域的元素(两个重载)
l1.erase(l1.begin());  	 将l1的第一个元素删除。l1.erase(l1.begin(), l1.end());  将l1的从 begin()end() 之间的元素删除。

3.3 splice 函数

  • splice函数是list中的一个转移函数,将另外一个list中的元素转移到本list中。共有3个重载:

1、list1.splice(position, list2): 将list2中的所有元素剪贴到list1的position位置;

2、list1.splice(position, list2, iter): 将list2中某个位置的迭代器iter指向的元素剪贴到list1中的position位置;

3、list1.splice(position, list2, iter1, iter2): 将list2中的某一段位置iter1 ~ iter2的元素剪贴到list1中的position位置

void list_splice()
{list<int>mylist1, mylist2;for (int i = 1; i <= 4; i++) mylist1.push_back(i);for (int i = 1; i <= 4; i++) mylist2.push_back(i * 10);std::list<int>::iterator it1 = mylist1.begin();it1++;mylist1.splice(it1, mylist2);  // 转移mylist2到it1位置之前// 注意:此时链表2就为空链表了 所以splice名字叫做转移for (auto& e : mylist1){cout << e << " ";}
}// 输出:1 10 20 30 40 2 3 4

四:list容器底层实现

容器的底层是用双向链表实现的,甚至一些 STL 版本中(比如 SGI STL),list 容器的底层实现使用的是双向循环链表。

头指针,使用链表存储数据,并不会将它们存储到一整块连续的内存空间中。恰恰相反,各元素占用的存储空间(又称为节点)是独立的、分散的,它们之间的线性关系通过指针来维持。

在这里插入图片描述

4.1 list 容器节点结构

双向链表的各个节点中存储的不仅仅是元素的值,还应包含 2 个指针,分别指向前一个元素和后一个元素。

template<class T>  
struct ListNode    // 当一个类不想要访问限定符限制的时候就用struct
{ListNode* _next;ListNode* _prev;T _data;ListNode(const T& data):_next(nullptr), _prev(nullptr), _data(data){}
};

注:为了方便阅读和理解,本文章所实现的list都省略了与本文核心内容不相关的内容,如果读者对此部分感兴趣,可查看 list 容器实现源码。

  • 本人的 gitee上具有本文实现的完整代码及测试代码,需要的可以自取【click】

5.2 list容器迭代器的底层实现

和 vector 这些容器迭代器的实现方式不同,由于 list 容器的元素并不是连续存储的,所以该容器迭代器中,必须包含一个可以指向 list 容器的指针,并且该指针还可以借助重载的 *、++、–、==、!= 等运算符,实现迭代器正确的递增、递减、取值等操作。

  • 所以我们就需要对迭代器进行封装!!

可以看到,迭代器的移动就是通过操作节点的指针实现的。

  • 注意:因为存在const迭代器 和 非const迭代器 所以这里我们的模版参数有三个一个为存储类型 一个为 引用返回类型 一个为 指针类型(const 和 非const)
// 通过模板,给不同的模板参数,让编译器帮我们实例化两个类
template<class T, class Ref, class Ptr>
struct ListIterator
{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}Self& operator++()  // 迭代器++返回自己 所以typedef一下{_node = _node->_next;return *this;}Self operator++(int)   // 后置++ 不能用引用返回了 tmp会销毁{Self tmp(*this);   // 默认的拷贝构造_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}Self operator--(int)   // 后置--{Self tmp(*this);_node = _node->_prev;return tmp;}Ptr operator->(){return &_node->_data;}Ref operator*(){return _node->_data;}bool operator!=(const Self& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}
};
  • 接下来是reverse_iterator的实现 注意反向迭代器的实现可以对正向迭代器进行复用 具体代码如下:
// 此处的参数就是正向迭代器了
template<class iterator, class Ref, class Ptr>
struct Reverse_ListIterator
{typedef Reverse_ListIterator<iterator, Ref, Ptr> Self;iterator _it;Reverse_ListIterator(iterator it):_it(it){}Ref operator*(){iterator tmp = _it;return tmp._node->_prev;}// 作用是为pos这种结构体服务的Ptr operator->(){return &_it._node->_prev->_data;}Self& operator++(){_it._node = _it._node->_prev;return *this;}Self operator++(int){Self tmp(*this);_it._node = _it._node->_prev;return tmp;}Self& operator--(){_it._node = _it._node->_next;return *this;}Self operator--(int){Self tmp(*this);_it._node = _it._node->_next;return tmp;}bool operator!=(const Self& it){return _it._node != it._it._node;    // 复用的结果}bool operator==(const Self& it){return _it._node == it._node;}};

5.2 list容器的主体实现

  • 以下是主体实现
template<class T>
class list
{typedef ListNode<T> Node;// 不符合迭代器的行为,无法遍历 无法进行++和*操作 所以我们要将迭代器封装成一个类进行运算符重载即可// typedef Node* iterator;
public:typedef ListIterator<T, T&, T*> iterator;   //规范迭代器typedef ListIterator<T, const T&, const T*> const_iterator;   // 通过模板,给不同的模板参数,让编译器帮我们实例化两个类// list的const迭代器是有东西的typedef Reverse_ListIterator<iterator, T&, T*> reverse_iterator;typedef Reverse_ListIterator<const_iterator, const T&, const T*> const_reverse_iterator;iterator begin(){return iterator(_head->_next);    // 匿名对象}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);    // 匿名对象}const_iterator end() const{return const_iterator(_head);}reverse_iterator rbegin(){return reverse_iterator(end());    // 匿名对象}reverse_iterator rend(){return reverse_iterator(begin());}list()   // 初始化哨兵卫的头节点{_head = new Node(T());   // 没有合适的默认构造函数可用 这里用匿名对象初始化_head->_next = _head;_head->_prev = _head;}void empty_init(){_head = new Node(T());_head->_next = _head;_head->_prev = _head;}list(const list<T>& lt){empty_init();for (const auto& e : lt){push_back(e);}}// 这里的赋值重载是现代写法list<T>& operator=(list<T> lt){swap(_head, lt._head);return *this;}~list(){clear();delete _head;}void push_back(const T& x){Node* newnode = new Node(x);    // 就不需要专门写个CreatNewNode的了   自动调用Node的构造函数/*newnode->_next = _head;newnode->_prev = _head->_prev;_head->_prev->_next = newnode;_head->_prev = newnode;*/insert(end(), x);}iterator insert(iterator pos, const T& x){Node* node = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;node->_next = cur;node->_prev = prev;prev->_next = node;cur->_prev = node;return iterator(node);}iterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;return iterator(next);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void clear(){list<T>::iterator it = begin();while (it != end()){it = erase(it);}}private:Node* _head;
};

总结

list的详细用法和底层实现就介绍到这里了如果有任何疑问都可以私信我,希望我们共同进步, 有错误还请在评论区指正!

相关文章:

C++STL容器系列(三)list的详细用法和底层实现

目录 一&#xff1a;介绍二&#xff1a;list的创建和方法创建list方法 三&#xff1a;list的具体用法3.1 push_back、pop_back、push_front、pop_front3.2 insert() 和 erase()3.3 splice 函数 四&#xff1a;list容器底层实现4.1 list 容器节点结构5.2 list容器迭代器的底层实…...

IEEE Latex模版踩雷避坑指南

参考文献 原Latex模版 \begin{thebibliography}{1} \bibliographystyle{IEEEtran}\bibitem{ref1} {\it{Mathematics Into Type}}. American Mathematical Society. [Online]. Available: https://www.ams.org/arc/styleguide/mit-2.pdf\bibitem{ref2} T. W. Chaundy, P. R. Ba…...

【C++】类与对象——多态详解

目录 一、多态的定义 二、重载、覆盖(重写)、隐藏(重定义)的对比 三、析构函数重写 四、C11 override 和 final 1. final 2. override 五、抽象类 六、多态的原理 一、多态的定义 多态是在不同继承关系的类对象&#xff0c;去调用同一函数&#xff0c;产生了不同的行为…...

WordPress建网站公司 建易WordPress建站

建易WordPress建网站公司是一家专业从事WordPress网站建设、网站维护、网站托管、运营推广和搜索引擎优化(SEO)等服务的公司。建易WordPress建网站公司提供多种服务&#xff0c;包括模板建站和定制网站&#xff0c;并且明码标价&#xff0c;价格透明&#xff0c;竭诚为全国各地…...

MySQL正则替换整个单词

\b 是正则表达式规定的一个特殊代码&#xff08;好吧&#xff0c;某些人叫它元字符&#xff0c;metacharacter&#xff09;&#xff0c;代表着单词的开头或结尾&#xff0c;也就是单词的分界处。虽然通常英文的单词是由空格&#xff0c;标点符号或者换行来分隔的&#xff0c;但…...

Java设计模式:享元模式实现高效对象共享与内存优化(十一)

码到三十五 &#xff1a; 个人主页 目录 一、引言二、享元设计模式的概念1. 对象状态的划分2. 共享机制 三、享元设计模式的组成四、享元设计模式的工作原理五、享元模式的使用六、享元设计模式的优点和适用场景结语 [参见]&#xff1a; Java设计模式&#xff1a;核心概述&…...

景源畅信电商:抖音开店步骤是什么?

随着社交媒体的兴起&#xff0c;抖音已经成为一个不可忽视的电商平台。许多人都希望通过抖音开店来实现自己的创业梦想。那么&#xff0c;抖音开店的具体步骤是什么呢?接下来&#xff0c;我们将详细阐述这一问题。 一、明确回答问题抖音开店的步骤主要包括&#xff1a;注册账号…...

Notepad++不显示CRLF的方法

View -> Show Symbol -> 去掉勾选 Show All Characters...

前端开发工程师——AngularJS

一.表达式和语句 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-w…...

【AI算法岗面试八股面经【超全整理】——概率论】

AI算法岗面试八股面经【超全整理】 概率论信息论机器学习CVNLP 目录 1、古典概型、几何概型2、条件概率、全概率公式、贝叶斯公式3、先验概率、后验概率4、离散型随机变量的常见分布5、连续型随机变量的常见分别6、数学期望、方差7、协方差、相关系数8、独立、互斥、不相关9.大…...

vue3 使用vant

使用前提&#xff1a; vite创建的vue3项目 vanthttps://vant-ui.github.io/vant/#/zh-CN/home npm i vant 引入样式&#xff1a; main.js import vant/lib/index.css vant封装 import { showLoadingToast,closeToast,showDialog,showConfirmDialog } from vant;export func…...

网络请求客户端WebClient的使用

在 Spring 5 之前&#xff0c;如果我们想要调用其他系统提供的 HTTP 服务&#xff0c;通常可以使用 Spring 提供的 RestTemplate 来访问&#xff0c;不过由于 RestTemplate 是 Spring 3 中引入的同步阻塞式 HTTP 客户端&#xff0c;因此存在一定性能瓶颈。根据 Spring 官方文档…...

unity制作app(9)--拍照 相册 上传照片

1.传输照片&#xff08;任何较大的数据&#xff09;都需要扩展服务器的内存空间。 2.还需要base64编码 2.1客户端发送位置的编码 2.2服务器接收部分的代码...

【busybox记录】【shell指令】mkfifo

目录 内容来源&#xff1a; 【GUN】【mkfifo】指令介绍 【busybox】【mkfifo】指令介绍 【linux】【mkfifo】指令介绍 使用示例&#xff1a; 创建管道文件 - 创建的时候同时指定文件权限 常用组合指令&#xff1a; 指令不常用/组合用法还需继续挖掘&#xff1a; 内容来…...

使用Jmeter进行性能测试的基本操作方法

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…...

Linux学习笔记(epoll,IO多路复用)

Linux learning note 1、epoll的使用场景2、epoll的使用方法和内部原理2.1、创建epoll2.2、使用epoll监听和处理事件 3、示例 1、epoll的使用场景 epoll的英文全称是extend poll&#xff0c;顾名思义是poll的升级版。常见的IO复用技术有select&#xff0c;poll&#xff0c;epo…...

STM32定时器及输出PWM完成呼吸灯

文章目录 一、STM32定时器原理1、基本定时器2、通用定时器&#xff08;1&#xff09;时钟源&#xff08;2&#xff09;预分频器PSC&#xff08;3&#xff09;计数器CNT&#xff08;4&#xff09;自动装载寄存器ARR 3、高级定时器 二、PWM工作原理三、控制LED以2s的频率周期性地…...

海外仓管理系统费用解析:如何选择高性价比的海外仓系统

海外仓作为链接国内商家和海外市场的重要环节&#xff0c;其重要性自然是不言而喻的。 对于众多中小型海外仓来说&#xff0c;如何在保证服务质量的同时降低运营成本&#xff0c;就成了大家关注的焦点。今天我们就从海外仓管理系统的费用这个角度&#xff0c;来帮助大家分析一…...

深度学习之学习率调度器Scheduler介绍

调度器是深度学习训练过程中非常重要的一部分,它用于动态调整模型的学习率,从而提高训练效率和最终性能。 1. 为什么需要学习率调度器? 深度学习训练中,学习率是一个非常关键的超参数。合适的学习率可以确保模型快速收敛并获得良好的性能。 但是在训练过程中,最优的学习率会随…...

蓝桥杯-AB路线(详细原创)

问题描述&#xff1a; 有一个由 N M 个方格组成的迷宫&#xff0c;每个方格写有一个字母 A 或者 B。小蓝站在迷宫左上角的方格&#xff0c;目标是走到右下角的方格。他每一步可以移动到上下左右相邻的方格去。 由于特殊的原因&#xff0c;小蓝的路线必须先走 K 个 A 格子、再…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...