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

【C++】C++中的list

一、介绍

       官方给的 list的文档介绍

简单来说就是:

        list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素。

这个时候大家可能觉得,都是有序列表,那么和vector有什么区别和对比吗?实际上和我们学习数据结构时对链表和数组的对比很像,我来介绍一下:
 

std::list

  • std::list 是一个双向链表,支持在常数时间内对序列的任何位置进行插入和删除操作。
  • 由于其链表的性质,list 不支持快速随机访问,即不能通过索引以常数时间访问元素(例如 list[5] 是非法的)。
  • list 更适用于元素频繁插入和删除的场景,尤其是在序列的头部和尾部,或者你不需要通过索引来访问元素。
  • 迭代器失效问题较少,插入和删除操作不会导致除了被操作的元素之外的迭代器失效。
  • 在内存中不是连续存储的,因此不支持指针算术运算,并且可能导致较差的缓存性能。

std::vector

  • std::vector 是一个动态数组,可以在末尾快速地添加或移除元素(均摊常数时间复杂度),而且支持快速随机访问,即可以以常数时间访问任意位置的元素。
  • vector 的中间或开头插入或删除元素可能会导致较高的性能开销,因为这些操作需要移动插入点之后(或删除点之后)的所有元素。
  • 适用于需要经常随机访问元素,但对于插入和删除的频率较低的场景。
  • 在内存中是连续存储的,这意味着可以使用指针算术,并且有助于优化缓存使用。
  • vector 重新分配更大的内存空间以容纳更多元素时,所有的迭代器、引用和指针都可能失效。

那么list到底长什么样子呢?上图片,是不是就好理解了

二、list的使用

        作为STL(标准模板库)中的一个类,我们这篇blog的任务就是学习其的使用。

构造函数

构造函数接口说明
list()构造空的list
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)[first, last)区间中的元素构造list

上面虽然用了不少代名词,我们直接上代码例子分析自然就清楚了,分析在代码中。

(如果迭代器看不懂可以看这一篇【C++】C++中的vector-CSDN博客,里面详细介绍了)

#include <iostream>
#include <list>
using namespace std;
int main()
{//list<int> l1; // 构造空的l1list<int> l2(4, 100); // l2中放4个值为100的元素list<int> l3(l2.begin(), l2.end()); // 用l2的[begin(), end())左闭右开的区间构造l3list<int> l4(l3); // 用l3拷贝构造l4// 以数组为迭代器区间构造l5int array[] = { 16,2,77,29 };std::list<int> l5(array, array + sizeof(array) / sizeof(int));// 用迭代器方式打印l5中的元素for (std::list<int>::iterator it = l5.begin(); it != l5.end(); it++)std::cout << *it << " ";std::cout << endl;// C++11范围for的方式遍历for (auto& e : l5)std::cout << e << " ";std::cout << endl;return 0;
}

其实我们可以看出来,list这个类和之前的使用类的方法是基本一致的,不过他需要一个<int>来确定这个序列容器的类型,比如int,char....,就是list<int>可以当成一个整体,和vector很像。

list iterator的使用

         此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点
函数声明

接口说明

begin end

获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置 的iterator/const_iterator

rbegin + rend

获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的 reverse_iterator

 PS:

1. begin end 为正向迭代器,对迭代器执行 ++ 操作,迭代器向后移动
2. rbegin(end) rend(begin) 为反向迭代器,对迭代器执行 ++ 操作,迭代器向前移动
上代码:
#include <iostream>
#include <list>
using namespace std;void print_list(const list<int>& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象// 保护数据通过将l声明为常量引用,我们保证了在print_list函数内部无法修改列表l的内容。// 这意味着无法添加、删除或修改列表中的任何元素。这是一种良好的编程实践,// 特别是当函数的目的仅仅是读取数据而不修改数据时。for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";//如果不同const 就通过//*it = 10; 编译不通过}cout << endl;
}
int main()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));// 使用正向迭代器正向list中的元素for (list<int>::iterator it = l.begin(); it != l.end(); ++it)cout << *it << " ";cout << endl;// 使用反向迭代器逆向打印list中的元素for (list<int>::reverse_iterator it = l.rbegin(); it != l.rend(); ++it)cout << *it << " ";cout << endl;return 0;
}

常用的成员方法

list capacity

函数声明

接口说明

empty
检测 list 是否为空,是返回 true ,否则返回 false
size
返回 list 中有效节点的个数

列表元素访问

函数声明

接口说明

empty
检测 list 是否为空,是返回 true ,否则返回 false
size
返回 list 中有效节点的个数

 list modifiers

函数声明
接口说明
push_frontlist首元素前插入值为val的元素
pop_front删除list中第一个元素
push_backlist尾部插入值为val的元素
pop_back删除list中最后一个元素
insertlist position 位置中插入值为val的元素
erase 删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素
上代码看实现:
#include <iostream>
#include <list>
#include <vector>
using namespace std;
void PrintList(list<int>& l)
{for (auto& e : l)cout << e << " ";cout << endl;
}
//===============================================================
// push_back/pop_back/push_front/pop_front
void TestList1()
{cout << "TestList1()" << endl;int array[] = { 1, 2, 3 };list<int> L(array, array + sizeof(array) / sizeof(array[0]));// 在list的尾部插入4,头部插入0L.push_back(4);L.push_front(0);PrintList(L);// 删除list尾部节点和头部节点L.pop_back();L.pop_front();PrintList(L);
}
//================================================================
// insert /erase 
void TestList2()
{cout << "TestList2()" << endl;int array1[] = { 1, 2, 3 };list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));// 获取链表中第二个节点auto pos = ++L.begin();cout << *pos << endl;// 在pos前插入值为4的元素L.insert(pos, 4);PrintList(L);// 在pos前插入5个值为5的元素L.insert(pos, 5, 5);PrintList(L);// 在pos前插入[v.begin(), v.end)区间中的元素vector<int> v {7, 8, 9 };L.insert(pos, v.begin(), v.end());PrintList(L);// 删除pos位置上的元素L.erase(pos);PrintList(L);// 删除list中[begin, end)区间中的元素,即删除list中的所有元素L.erase(L.begin(), L.end());PrintList(L);
}
// resize/swap/clear
void TestList3()
{cout << "TestList3()" << endl;// 用数组来构造listint array1[] = { 1, 2, 3 };list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));PrintList(l1);// 交换l1和l2中的元素list<int> l2;l1.swap(l2);PrintList(l1);PrintList(l2);
//使用resize将l2的大小先增加到5个元素,所有新添加的元素都将被赋值为99l2.resize(5, 99);PrintList(l2);// 将l2中的元素清空l2.clear();cout << l2.size() << endl;
}int main()
{TestList1();TestList2();TestList3();return 0;
}

        好了,目前通过上面这一段精简的代码,我们把常用的成员方法基本解决了,但是list的成员方法实在太多,很多操作都是很特殊,不常见的,但是如果刚好需要又非常方便,所以就是可以在需要的时候查官方文档。

list的迭代器失效

在之前我们学习过vector的迭代器会有失效的情况,原因很简单,指针失效了,那么list会不会有这种情况呢?答案是有的,前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。所以影响相对vector来说比较小。

理解了吗?两段代码来检测一下大家

#include <iostream>
#include <list>
using namespace std;void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it);++it;}
}void TestListIterator2()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); // it = l.erase(it);}
}int main()
{TestListIterator1();TestListIterator2();return 0;
}

是 TestListIterator1 出错 还是 TestListIterator2 出错?

如果你一眼就看出了,那么恭喜你,你掌握了。

其实很简单,第一个当调用erase(it)后,it被删除,使得it失效。尝试在失效的迭代器上进行操作(比如递增++it)是未定义行为。

      第二个,l.erase(it++):这里使用了“后置递增”运算符,它创建了it的一个副本,然后将副本传递给erase方法。erase删除了当前迭代器指向的元素,然后it被递增,指向下一个元素。因为it在递增前已经复制给erase,所以即使在删除当前元素后,递增操作是在一个新的、未被修改的迭代器上进行的,这保证了迭代器的有效性。或者可以这样写,等价的it = l.erase(it);erase函数返回下一个有效的迭代器,然后将其赋值给it。这样,it始终保持有效,且指向当前元素的下一个元素。

三、结语

到此为止,我们已经把list的基本使用方法学习结束了,list的成员方法十分丰富,这篇文章就是介绍了常用的,让大家基本会使用,目前你也可以用这种双向列表来实现一些复杂的算法,我在下面了可以给大家写一个。等我有时间再出一篇,模拟实现list的blog,理解他的底层实现,有缘再见,朋友!

实现的经典算法

约瑟夫环问题(Josephus Problem)。这个问题的一个版本可以描述如下:N个人围成一圈,从第一个人开始报数,每报到M时,该人被淘汰,接着从下一个人开始继续报数,直到所有人都被淘汰。任务是按顺序输出被淘汰人的编号。

#include <iostream>
#include <list>
using namespace std;void JosephusProblem(int N, int M) {// 初始化人员列表,编号从1到Nlist<int> people;for (int i = 1; i <= N; ++i) {people.push_back(i);}auto it = people.begin(); // 迭代器指向第一个人while (!people.empty()) {// 模拟报数,M-1次移动迭代器(因为从当前人开始报数)for (int count = 1; count < M; ++count) {++it;// 如果迭代器超过了末尾,重新从头开始if (it == people.end()) {it = people.begin();}}// 报到M,移除当前人,并输出编号cout << *it << " ";it = people.erase(it); // erase返回下一个元素的迭代器// 如果列表不为空,但迭代器已经到达末尾,需要重新指向开头if (it == people.end() && !people.empty()) {it = people.begin();}}cout << endl;
}int main() {int N = 7; // 人数int M = 3; // 报数淘汰JosephusProblem(N, M);return 0;
}

相关文章:

【C++】C++中的list

一、介绍 官方给的 list的文档介绍 简单来说就是&#xff1a; list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中…...

uniapp:Hbuilder没有检测到设备请插入设备或启动模拟器的问题解决

问题 使用模拟器调试运行项目时&#xff0c;出现以下提示&#xff0c;“没有检测到设备&#xff0c;请插入设备或启动模拟器后点击刷新再试”。排查了一天最终找到原因。 解决 已确认模拟器是已经正常启动&#xff0c;并且Hbuilder设置中的adb路径和端口都配置没有问题&#…...

基于RBF的时间序列预测模型matlab代码

整理了基于RBF的时间序列预测模型matlab代码&#xff0c; 包含数据集。采用了四个评价指标R2、MAE、MBE、MAPE对模型的进行评价。RBF模型在数据集上表现非常好。 训练集数据的R2为&#xff1a;0.99463 测试集数据的R2为&#xff1a;0.96973 训练集数据的MAE为&#xff1a;0.…...

vue vue3 手写 动态加载组件

效果展示 一、需求背景&#xff1a; # vue3 项目涉及很多图表加载、表格加载 #考虑手写一个动态加载组件 二、实现思路 通过一个加载状态变量&#xff0c;通过v-if判断&#xff0c;加载状态的变量等于哪一个&#xff0c;动态加载组件内部就显示的哪一块组件。 三、实现效果…...

HTML:表单

目录 案例&#xff1a; 一、form标签 二、input标签 三、textarea标签 四、select标签 五、fieldset 标签 案例&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>报名表</title> </head> &l…...

即插即用篇 | YOLOv5/v7引入Haar小波下采样 | 一种简单而有效的语义分割下采样模块

本改进已集成到 YOLOv5-Magic 框架。 下采样操作如最大池化或步幅卷积在卷积神经网络(CNNs)中被广泛应用,用于聚合局部特征、扩大感受野并减少计算负担。然而,对于语义分割任务,对局部邻域的特征进行池化可能导致重要的空间信息丢失,这有助于逐像素预测。为了解决这个问题…...

Plonky2.5:在Plonky2中验证Plonky3 proof

1. 引言 Plonky2.5为QED Protocol团队主导的项目&#xff0c;定位为&#xff1a; 在Plonky2 SNARK中验证Plonky3 STARK proof。 从而实现Plonky系列的递归证明。 开源代码实现见&#xff1a; https://github.com/QEDProtocol/plonky2.5https://github.com/Plonky3/Plonky3&a…...

卷积通用模型的剪枝、蒸馏---剪枝篇(此处以deeplabv3+为例,可根据模型自行定制剪枝层)

之后的两篇文章是对前段时间工作的一个总结。 一、环境配置 1.1、文章以b导的代码为模板,环境配置比较简单(第二篇蒸馏篇结束后会放置剪枝蒸馏配置好的百度网盘链接),其他算法自行配置,在剪枝之前,需要保证算法能够在本地跑通。 B导链接: https://github.com/bubbliiiin…...

使用Ollama在本地运行AI大模型gemma

1.下载&#xff1a; https://github.com/ollama/ollama/releases 2.配置环境变量 我的电脑-右键-属性-系统-高级系统设置-环境变量-【系统环境变量】新建 变量名&#xff1a;OLLAMA_MODELS &#xff08;固定变量名&#xff09; 变量值&#xff1a;E:\Ollama\Lib &#xff0…...

【IC前端虚拟项目】时序面积优化与综合代码出版本交付

【IC前端虚拟项目】数据搬运指令处理模块前端实现虚拟项目说明-CSDN博客 到目前为止,我们完成了第一版综合,那么就可以打开报告看一下了,一看就会发现在1GHz时钟下时序真的很差(毕竟虚拟项目里使用的工艺库还是比较旧的,如果用12nm、7mn会好很多): Timing Path Group cl…...

windows版本-idea中下载的java版本在哪

1、点击idea的file-projectStructure 进入&#xff1a; 通过电脑目录进入该目录 找到bin目录&#xff0c;copy该目录地址 copy下来之后设置到系统环境变量中...

设计模式:创建者模式

定义 创建者模式&#xff08;Builder Pattern&#xff09;&#xff0c;又称建造者模式&#xff0c;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式。该模式允许将一个复杂对象的构建与它的表示分离&#xff0c;这样同样的构建过程可以创建不同的表示。创建者…...

【linux】基础IO(四)

在上一篇基础IO中我们主要讲述了文件再磁盘中的存储&#xff0c;当然我们说的也都只是预备知识&#xff0c;为这一篇的文件系统进行铺垫。 目录 搭文件系统的架子&#xff1a;填补细节&#xff1a;inode&#xff1a;datablock[]: 更上层的理解&#xff1a; 搭文件系统的架子&a…...

集合框架(数组,Arrays.sort,list,map,set,stack,queue)蓝桥杯习题

前言(基本知识) List集合 有序&#xff0c;接口&#xff0c; List<引用数据类型> listnew ArrayList<>(); 方法&#xff1a; add() size() get()//索引index从0开始&#xff0c;返回对应的值 isEmpty()判断是否包含该元素,不包含返回true&#xff0c;包含返…...

【C++基础】运算符和流程控制语句

C中的运算符和流程控制语句 一、运算符1. C和Java在通用运算符中的不同之处对比2. C中的位运算符2.1 移位运算符2.2 位逻辑运算符 3. 运算时的类型转换总结3.1 隐式类型转换3.2 显式类型转换&#xff08;强制类型转换&#xff09; 4. 注意 二、流程控制语句1. C和Java在通用流程…...

AOF文件重写

1.2.3.AOF文件重写 因为是记录命令&#xff0c;AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作&#xff0c;但只有最后一次写操作才有意义。通过执行bgrewriteaof命令&#xff0c;可以让AOF文件执行重写功能&#xff0c;用最少的命令达到相同效果。 如图&am…...

第四次面试总结 — 嘉和智能 - 全栈开发

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对专栏 “本人真实面经” 很感兴趣o (ˉ▽ˉ&#xff1b;) 专栏 —— 本人真实面经&#xff0c;更多真实面试经验&#xff0c;中大厂面试总结等您挖掘 目录 总结&#xff08;非详细&#xff09; 面试内…...

tx-lcn使用

tx-lcn是啥 tx-lcn是一个分布式事务框架&#xff0c;有两个模块组成管理端&#xff08;server&#xff09;和client端。 管理端用于分布式事务的注册&#xff0c;事务消息接收&#xff0c;事务消息下发等管理工作。 client端包括事务发起方&#xff0c;事务参与方。 LCN名称是…...

oracle恢复异常处理

问题现象&#xff1a; RMAN> 2> 3> 4> 5> 6> 7> 8> 9> 10> 11> 12> 13> 14> 15> 16> 17> 18> 19> 20> 21> 22> 23> 24> using target database control file instead of recovery catalog allocate…...

谈谈什么是 Redis

&#x1f525;博客主页&#xff1a;fly in the sky - CSDN博客 &#x1f680;欢迎各位&#xff1a;点赞&#x1f44d;收藏⭐️留言✍️&#x1f680; &#x1f386;慢品人间烟火色,闲观万事岁月长&#x1f386; &#x1f4d6;希望我写的博客对你有所帮助,如有不足,请指正&#…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

web vue 项目 Docker化部署

Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段&#xff1a; 构建阶段&#xff08;Build Stage&#xff09;&#xff1a…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...

【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验

Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

SpringAI实战:ChatModel智能对话全解

一、引言&#xff1a;Spring AI 与 Chat Model 的核心价值 &#x1f680; 在 Java 生态中集成大模型能力&#xff0c;Spring AI 提供了高效的解决方案 &#x1f916;。其中 Chat Model 作为核心交互组件&#xff0c;通过标准化接口简化了与大语言模型&#xff08;LLM&#xff0…...

软件工程 期末复习

瀑布模型&#xff1a;计划 螺旋模型&#xff1a;风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合&#xff1a;模块内部功能紧密 模块之间依赖程度小 高内聚&#xff1a;指的是一个模块内部的功能应该紧密相关。换句话说&#xff0c;一个模块应当只实现单一的功能…...