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

C++ List 容器详解:迭代器失效、排序与高效操作

目录

  • 前言
  • 一、list的接口使用
    • 1.1 构造+迭代器+范围for
    • 1.2 find+insert+erase+list的迭代器失效问题
    • 1.3 reverse
    • 1.4 sort + 迭代器分类
      • 1.4.1 sort相关性能分析
    • 1.5 merge
    • 1.6 unique
    • 1.7 remove 和 remove_if
    • 1.8 splice
  • 结语

在这里插入图片描述

在这里插入图片描述

🎬 云泽Q:个人主页

🔥 专栏传送入口: 《C语言》《数据结构》《C++》《Linux》

⛺️遇见安然遇见你,不负代码不负卿~

前言

大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~

一、list的接口使用

STL库中的list是个带头双向循环链表,所以其能在任意位置进行插入删除,不需要挪动数据
在这里插入图片描述
STL库中还有一个单链表叫forward_list,只不过用的很少
在这里插入图片描述
list的构造
在这里插入图片描述
list的迭代器是双向的,可以正着走也可以倒着走
在这里插入图片描述
且list也没有reserve的概念了,毕竟没有扩容了,也没有capacity了

单链表的迭代器就没有rbegin,rend,只有begin,end,只支持加加,不支持减减
在这里插入图片描述
list访问数据也不支持下标方括号了,因为其底层不是连续的物理空间,支持方括号的代价就太高了,只支持迭代器访问
在这里插入图片描述
插入删除支持的接口很多

1.1 构造+迭代器+范围for

在这里插入图片描述
这里说一下图中范围for的代码逻辑:
一、范围 for 的本质:迭代器遍历的 “语法糖”
C++11 引入的范围 for(for (auto e : 容器))是编译器提供的语法简化,其底层完全依赖迭代器遍历。对于代码中的 for (auto e : lt2),编译器会自动将其转换为基于迭代器的循环结构。

二、底层转换的详细步骤
以 for (auto e : lt2) 为例(lt2 是 std::list),编译器会执行以下转换:

  1. 获取迭代器的起始和结束位置
    调用容器的 begin() 和 end() 成员函数,分别获取起始迭代器(指向第一个元素)和结束迭代器(指向 “尾后位置”,不存储实际元素)。
    等价代码:
auto __begin = lt2.begin(); // 起始迭代器,指向第一个元素
auto __end = lt2.end();     // 结束迭代器,指向尾后位置
  1. 循环遍历逻辑以 “起始迭代器!= 结束迭代器” 为条件,循环执行以下操作:
    • 解引用迭代器 * __begin,获取当前元素的值;
    • 将该值赋值给循环变量 e(auto 会推导为 int 类型,因为 lt2 存储的是 int);
    • 迭代器自增(++__begin),移动到下一个元素。
      等价代码:
for (; __begin != __end; ++__begin) {auto e = *__begin; // 解引用迭代器,获取元素值cout << e << " "; // 输出元素
}

三、范围 for 的适用条件
范围 for 能工作的核心前提是:

  • 容器(或对象)必须支持 **begin() 和 end() 操作 **(可以是成员函数,也可以是全局的 std::begin/std::end 函数);
  • 迭代器必须支持前置自增(++)、不等于比较(!=)、解引用( * ) 操作。

以 std::list 为例:

  • 它的 begin()/end() 是成员函数,返回双向迭代器;
  • 该迭代器支持 ++(移动到下一个节点)、!=(判断是否到尾后)、*(获取节点值),因此完全兼容范围 for。

四、与手动迭代器遍历的对比
手动迭代器遍历的代码是:

list<int>::iterator it = lt2.begin();
while (it != lt2.end()) {cout << *it << " ";++it;
}

范围 for 只是将上述逻辑隐藏在编译器转换中,让代码更简洁、可读性更高。

1.2 find+insert+erase+list的迭代器失效问题

list是没有提供find接口的,若想使用find就要使用算法库中的find
在这里插入图片描述
在这里插入图片描述
list的insert就不会出现迭代器失效了,由于list是带头双向循环链表,在3这个结点前插入数据,不存在挪动数据扩容之类的问题
在这里插入图片描述
但是list的erase会出现迭代器失效,因为是把pos指向的底层的结点删除了
在这里插入图片描述
所以erase后就不能使用指向pos位置的迭代器了

1.3 reverse

list还有一些其他相关的操作
在这里插入图片描述
list单独实现了一个自己的逆置函数,就是把带头双向循环链表反转一下
在这里插入图片描述

1.4 sort + 迭代器分类

这里list容器内部实现了自己的sort函数,而不是用算法库中的sort的原因也牵扯到迭代器的分类
功能角度:按操作能力(是由容器的底层结构决定)
在这里插入图片描述
list中拥有的迭代器类型
在这里插入图片描述
forward_list拥有的迭代器类型
在这里插入图片描述
vector拥有的迭代器类型
在这里插入图片描述

使用角度:按遍历方向 + 读写权限
在这里插入图片描述
其次就是具体原因:
一、std::list的底层结构与迭代器限制
std::list是 C++ STL 中双向链表的实现,其底层结构是:

  • 每个元素被封装在独立的节点中,节点包含:数据域(存储元素)、前驱指针(指向前一个节点)、后继指针(指向后一个节点);
  • 节点在内存中非连续存储(地址随机),只能通过指针依次访问(无法直接跳转到第 n 个元素)。

这种结构直接决定了std::list的迭代器类型是 “双向迭代器(Bidirectional Iterator)”,它仅支持以下操作:

  • 自增(++it):移动到下一个节点;
  • 自减(–it):移动到上一个节点;
  • 解引用( * it):获取当前节点的元素;
  • 不等于比较(it1 != it2):判断是否指向同一个节点。

关键限制:双向迭代器不支持随机访问(如it + 3、it - 2这类直接跳转操作),也无法通过[]运算符访问指定位置的元素。

二、算法库std::sort的核心依赖:随机访问迭代器
标准库中的std::sort(定义在< algorithm >中)是一个通用排序算法,其内部实现通常基于快速排序(或其改进版,如 introsort),这类算法的高效性严重依赖于随机访问能力,具体表现为:

  1. 频繁访问 “中间元素”:快速排序需要选取 “枢轴元素”(通常是序列中间的元素),这要求能通过begin + (end - begin) / 2直接定位到中间位置;
  2. 高效交换元素:排序过程中需要交换任意位置的元素,依赖迭代器的+、-操作快速计算元素位置;
  3. 计算区间长度:需要通过end - begin快速获取序列长度,用于判断是否继续递归排序。

这些操作都要求迭代器必须是 “随机访问迭代器(Random Access Iterator)”—— 这种迭代器支持+、-、[]、<、>等操作(如std::vector、std::array的迭代器)。

三、std::list用std::sort报错的根本原因:迭代器类型不兼容
由于std::list的迭代器是双向迭代器,而std::sort要求随机访问迭代器,两者的 “能力集” 不匹配:

  • 当调用std::sort(list.begin(), list.end())时,编译器会检查迭代器类型是否满足std::sort的模板要求(通过迭代器特性std::iterator_traits判断);
  • 由于双向迭代器缺少std::sort必需的随机访问操作(如+、-),编译器会触发模板实例化失败,报 “迭代器类型不匹配” 的错误(通常提示 “没有与操作符 + 匹配的重载”)。

四、std::list自带sort成员函数的原因:适配链表特性的高效实现
std::list的成员函数sort是专门为链表结构设计的,其核心逻辑基于归并排序(Merge Sort),这种算法的特性完美适配链表:

  1. 归并排序的核心操作是 “合并两个有序序列”,对于链表而言,合并仅需调整节点的前驱 / 后继指针(无需移动元素本身),时间复杂度为 O (n),效率极高;
  2. 归并排序不需要随机访问,仅需顺序遍历(通过双向迭代器的++操作即可完成),完全兼容链表的迭代器能力;
  3. 避免元素拷贝开销:链表节点的 “值” 通常存储在堆上,归并排序通过指针调整完成排序,无需复制元素(而std::sort对链表排序时,每次交换都需要拷贝元素,开销极大)。

因此,list::sort的时间复杂度是 O (n log n),且常数因子远低于 “强行用std::sort对链表排序” 的情况(后者即使能编译,也会因频繁的迭代器移动和元素拷贝导致效率骤降)。

1.4.1 sort相关性能分析

在这里插入图片描述
算法库中sort的形参名称也暗示了什么类型的迭代器适用该函数

list<int> lt2 = { 1,2,3,4,5 };
//sort(lt2.begin(), lt2.end());不支持,会报错
lt2.sort();

再比如说算法库中的reverse
在这里插入图片描述
这里reverse要求双向迭代器,但随机迭代器(例如string的迭代器)也可以使用逆置的原因是一种继承关系

继承就是子类是一个特殊的父类(存在一个父子关系,父类满足的子类都满足),随机迭代器是一个特殊的双向迭代器,随机迭代器也是一个特殊的单向迭代器,双向迭代器也是一个特殊的单向迭代器。这些关系就如正方形是一个特殊的长方形一样
在这里插入图片描述
这里就意味着要求随机只能传随机,要求双向既可以传双向也可以传随机,如果形参是一个Forward Iterator,既可以传单向也可以传双向也可以传随机

后面我会单独写一篇继承的文章,继承还做了一层高度抽象
在这里插入图片描述
InputIterator就是只写,OutputIterator就是只读,可以认为所有的迭代器都是只写/只读的迭代器

就比如说算法库中的find
在这里插入图片描述

1.5 merge

归并要求两个链表必须是有序的(归并前先排序,和归并排序一样),将一个链表归并到另外一个链表
在这里插入图片描述
在这里插入图片描述

1.6 unique

在这里插入图片描述
去重也是要求先排序的,把重复的数据去掉只留下一个,过程类似双指针去重,一前一后两个指针,若两个指针指向的值相同,就把后一个值删掉
在这里插入图片描述
如图不经过排序,不相邻的值是无法完成去重的
在这里插入图片描述

1.7 remove 和 remove_if

在这里插入图片描述
remove和erase有相似支持,erase是给一个迭代器位置去删除迭代器指向的值,remove是给一个值去删除
在这里插入图片描述
remove_if设计一个仿函数问题,满足某个条件才删除(该条件用仿函数实现)

1.8 splice

在这里插入图片描述
它叫接合,本质上是一种转移,可以把一个链表里的结点挪到另外一个链表中去
在这里插入图片描述
但是其也可以在当前链表进行挪动,不仅限于两个链表之间的挪动
在这里插入图片描述


结语

在这里插入图片描述

http://www.dtcms.com/a/617791.html

相关文章:

  • 婚纱网站wordpress微商模板
  • GPT问答:泛型、哈希表与缓存、命名参数。251116
  • 免费学软件的自学网站保健品网站建设流程
  • 网络访问流程:HTTPS + TCP + IP
  • 智能体AI、技术浪潮与冲浪哲学
  • 基于 PyTorch + BERT 意图识别与模型微调
  • 沃尔沃公司网站建设微信官方网站建设
  • 网站备案域名怎么买找在农村适合的代加工
  • 42 解决一些问题
  • Claude Code 功能+技巧
  • 基于人脸识别和 MySQL 的考勤管理系统实现
  • AUTOSAR_CP_OS-Operating System for Multi-Core:多核操作系统
  • 什么是 “信任模型” 和 “安全假设”?
  • 【秣厉科技】LabVIEW工具包——HIKRobot(海康机器人系列)
  • 网易UU远程全功能技术解构:游戏级性能突围与安全边界探析
  • 蓝桥杯第八届省赛单片机设计完全入门(零基础保姆级教程)
  • 搭建网站分类建立名词
  • 没有域名的网站wordpress占用资源
  • RPA+AI双剑合璧!小红书商品笔记自动发布,效率提升2000%[特殊字符]
  • 19.传输层协议UDP
  • linux服务-rsync+inotify文件同步-rsync
  • 机器学习之ravel()的作用
  • Wi-Fi 7路由器性能分析:从传输速率到多设备协同全面解析
  • 【Java手搓RAGFlow】-1- 环境准备
  • 审计部绩效考核关键指标与综合评估方法
  • Photoshop - Photoshop 工具栏(29)钢笔工具
  • 营销型网站策划方案大德通众包做网站怎么样
  • 使用 Web Workers 提升前端性能:让 JavaScript 不再阻塞 UI
  • HTTP与HTTPS深度解析:从明文传输到安全通信
  • 知识图谱与语言教育:AI如何重构小语种学习的基础设施