C++入门之list的使用详解

发布时间:2026/10/15 21:40:12

C++入门之list的使用详解 前言std::list头文件list是 C 标准库里的双向链表doubly linked list。 它最核心的特性只有一句话在任意位置插入和删除元素都是常数时间且除了被删除的那个迭代器 其他迭代器和指向元素的引用全部保持有效。大多数人对它的误解集中在两头。一头是把list当成更慢的vector 觉得既然vector有连续内存和缓存局部性list就没有存在价值 另一头是以为list是万能容器到处都用它结果在只需要尾部追加的场景下 白白付出每个元素两个指针的空间代价和一次次缓存缺失。list真正的价值在三个场景一是在中间频繁插入删除不用像vector那样搬移整段 二是元素很大或者是不可拷贝的插入删除时不想付出移动/拷贝成本 三是需要迭代器稳定性比如把迭代器存在别处、以后再回来操作这个元素。 本文讲清它的成员函数、独门武器splice/sort/merge/unique以及那些真会踩的坑。一、结构、复杂度与迭代器类别std::list的迭代器是双向迭代器bidirectional iterator支持和-- 但不支持it n、it - n、it[n]也不能用比较两个迭代器。 要在list上移动 n 步必须用std::advanceiterator或std::next复杂度是 O(n)。以 libstdc 为例它的std::list是带哨兵节点sentinel的双向循环链表 每个元素一个节点节点里除了数据就是next和prev两个指针。std::list的size()从 C11 起保证常数复杂度C98 允许某些实现做成线性。操作std::vectorstd::list尾部追加均摊 O(1)O(1)头部插入O(n)O(1)中间插入已有迭代器O(n)要搬移O(1)按值查找O(n)O(n)随机访问v[i]O(1)不支持每个元素的额外开销无两个指针实现相关缓存局部性好连续内存差节点分散插入是否失效迭代器是可能全部失效否删除是否失效迭代器被删位置之后全部失效只有被删的那个失效二、常用成员函数std::list的成员函数大致分四组类别成员函数构造与容量list()、size()、empty()、max_size()、clear()元素访问front()、back()修改push_front、push_back、pop_front、pop_back、emplace_front、emplace_back、emplace、insert、erase、resize、assign、swap链表专属splice、sort、merge、remove、remove_if、unique、reverse几个版本细节值得留意erase从 C11 起返回被删元素的下一个迭代器C98 返回void因此it l.erase(it);是惯用法。emplace_front和emplace_back从C17起返回新元素的引用push_front/push_back一直返回void。insert(pos, value)里定位是 O(n)要靠遍历走到pos只有插入本身是 O(1)。list没有operator[]也没有随机访问。三、独门武器splice、sort、merge、unique这四个成员函数是std::list区别于其他容器的关键。splice把一个链表或其中一段的节点整体摘到另一个链表上。 它不拷贝也不移动元素内容只改指针所以是常数时间整段范围版本是线性的因为要数长度。 这是把元素从一个容器移到另一个容器最便宜的方式void splice(const_iterator pos, list other); // 整个 othervoid splice(const_iterator pos, list other, const_iterator it); // 单个元素void splice(const_iterator pos, list other, const_iterator first, const_iterator last); // 区间前置条件有三个都必须满足other是右值引用版本同理 两条链表的get_allocator()必须相等否则行为未定义 区间版本里pos不能落在[first, last)内否则行为未定义 单元素版本里如果pos it或pos it函数什么都不做。sort是链表版本的排序。它不能换成std::sort因为std::sort需要随机访问迭代器 拿std::list的迭代器去调用会直接编译失败。成员sort是稳定stable的 复杂度 O(N log N) 次比较整个过程只改指针、不搬移元素libstdc 的实现是自底向上的归并排序。merge把另一条已排序的链表合并进来稳定且不拷贝元素。前提是两条链表都已按 同一个比较器排好序标准以前置条件的形式给出不满足时行为未定义 在 GCC/Clang 的实现里如果传进来的是自己函数什么都不做。unique只删除相邻的重复元素。不先排序的话它只能去掉连续出现的重复 分散在链表各处的相同元素一个都去不掉。它返回void接受一个可选的二元谓词。另外要注意remove/remove_if是成员函数返回void语义是删除所有满足条件的元素 和算法库里的std::remove/std::remove_if完全是两回事——后者不改变容器大小。四、完整示例// 适用C17GCC 13 / Clang 17 / MSVC 19.3x 均可编译// 编译g -stdc17 -Wall -Wextra -pedantic list_demo.cpp -o list_demo#include iterator // std::advance#include iostream#include list#include stringtemplate class Tvoid print(const std::string title, const std::listT l) {std::cout title : ;for (const T v : l) {std::cout v ;}std::cout (size l.size() )\n;}int main() {std::liststd::string a{cat, dog, fish};std::liststd::string b{ant, bee, cow};print(a, a);print(b, b);// 1) 插入。emplace_front / emplace_back 在 C17 起返回新元素的引用a.emplace_front(bat);a.emplace_back(std::string(eel));auto pos a.begin();std::advance(pos, 2); // list 迭代器没有 it n必须用 std::advancea.insert(pos, duck);print(a 插入后, a);// 2) splice把 b 的节点整体摘到 a 中不拷贝字符串b 变空auto where a.begin();where; // where 指向 cata.splice(where, b); // b 的所有元素插到 where 之前print(a splice 后, a);print(b splice 后, b);// 3) sort必须用成员版本比较器须满足严格弱序a.sort(); // 无参版本用 operator 升序print(a 排序后, a);// 4) unique只删相邻重复所以要先 sorta.insert(a.end(), cat);a.sort();a.unique();print(a unique 后, a);// 5) remove_if删除所有满足谓词的元素成员函数版本a.remove_if([](const std::string s) { return s.size() 3; });print(a 删除长度 3 后, a);// 6) 迭代器稳定性只删第一个元素指向其他元素的迭代器仍然有效auto anchored a.begin();anchored; // 指向第二个元素a.erase(a.begin()); // 只让 a.begin() 失效if (anchored ! a.end()) {std::cout erase 后锚点仍有效: *anchored \n;}// 7) merge两条已排序链表合并稳定被合并方变空std::liststd::string c{apple, banana};a.sort();c.sort();a.merge(c);print(a merge 后, a);print(c merge 后, c);return 0;}程序里每一步的结果b在splice后为空、unique前必须先sort、merge后源链表被清空都是std::list语义的直接体现跑一遍比读十遍记得更牢。常见坑点用std::sort排std::list❌std::sort(l.begin(), l.end());——std::sort要求随机访问迭代器list的迭代器是双向的 编译期就报错典型信息是找不到operator-。✅l.sort();或l.sort(comp);—— 用成员版本稳定且不搬移元素。unique之前没排序❌std::listint l{1, 2, 1, 2, 1}; l.unique();—— 结果还是1 2 1 2 1因为都不相邻。✅l.sort(); l.unique();—— 先排序让重复元素相邻。把list.remove和std::remove搞混❌std::remove(l.begin(), l.end(), 3);—— 算法版本不改变容器大小 只是把不匹配的元素往前挪尾部留下待删除的残骸。✅l.remove(3);—— 成员版本真的删除所有等于 3 的元素并更新size()。在splice的区间版本里把pos放在区间内❌l.splice(it, l, it, l.end());——pos落在[first, last)内这是未定义行为。✅ 让pos明确落在区间之外例如l.splice(l.end(), l, l.begin(), it);——pos是l.end()不在区间[l.begin(), it)里last本身不包含在区间内恒成立。跨分配器splice❌ 两条list用了不同的分配器例如一个是std::pmr::list绑到 pool A 另一个绑到 pool B仍直接splice—— 行为未定义。✅ 只对get_allocator()相等的两条链表做splice。边遍历边 erase 却不接管返回值❌for (auto it l.begin(); it ! l.end(); it) { if (bad(*it)) l.erase(it); }——it已经失效it是未定义行为症状通常是崩溃或跳过元素。✅for (auto it l.begin(); it ! l.end(); ) { if (bad(*it)) it l.erase(it); else it; }以为list的迭代器永远有效❌ 删除某元素后继续使用指向那个元素的迭代器 —— 它已经失效解引用是未定义行为。✅ 记住规则list的插入和splice不失效任何迭代器erase只失效被删元素的迭代器前提是这个元素确实还在链表里。在只需要尾部追加的场景硬用list❌ 把list当通用容器存十万个int然后逐个遍历求和 —— 每个节点多两个指针 节点在堆上分散遍历时缓存缺失远多于std::vector。✅ 需要顺序遍历、随机访问或连续内存时用std::vector 只有在中间频繁插入删除或必须保持迭代器稳定时才选std::list。总结主题关键结论迭代器类别双向迭代器没有/-/[]定位要std::advance插入删除已有迭代器时 O(1)且不失效其他迭代器splice常数时间搬运节点但要求分配器相同、pos不在被搬运区间内sort必须用成员版本稳定只改指针merge两条链表都要已排序合并后源链表变空unique只删相邻重复通常要先sort选型中间频繁增删、元素大、要迭代器稳定时选它否则优先vectorstd::list不是一个更好的vector而是一个在特定维度上做交换的容器 它用每个元素两个指针的额外空间、以及更差的缓存局部性 换来了常数时间的中间插入删除和极强的迭代器稳定性。 想清楚你的程序到底需要哪一边选型就不会纠结了。
延伸阅读

更多相关文章

2026/10/15 21:35:12

Cookie注入攻防实战:从原理到防护的完整指南

1. 从一次内部演练说起:为什么Cookie注入值得单独拿出来讲很多做Web安全测试的朋友,入门时接触的第一个漏洞类型大概率是SQL注入,而练手的第一个场景,往往是在URL参数或者表单输入框里构造payload。这很正常,因为GET和…

2026/10/15 21:35:12

Win7 64位下Python 2.7成功加载GDAL 2.4.4实战指南

简介:本资源是一份面向GIS开发初学者与Python2.7环境用户的GDAL地理信息库安装实战指南,聚焦Windows 7 64位平台下GDAL 1.9.2(兼容1.11.0)的完整部署方案。内容系统梳理两种主流安装路径:一是基于Visual Studio 2010编…

2026/10/15 21:35:12

面向端侧空间安全AI节点AiBrainBox-S应用于电力行业:多模态空间融合、动态风险区、身份-视觉轨迹关联、设备级实时安全推理

面向端侧空间安全AI节点AiBrainBox-S应用于电力行业:多模态空间融合、动态风险区、身份-视觉轨迹关联、设备级实时安全推理“面向端侧空间安全AI节点的 Industrial Scene Knowledge Package”1. 目前电力行业已经有哪些“知识”?分成5类:2. 最…

2026/10/15 22:45:59

用Pandas自动生成人力月报:Excel手工报表流程的完整替代方案

在人力数据分析这个方向看得多了,我发现真正把同行拖垮的往往不是分析思路,而是Excel里那套复制粘贴、VLOOKUP、透视表、调格式的流水线。每月初收各部门花名册、入职台账、离职台账,串数据就得大半天,交上去的报表老板只看几个数…

2026/10/15 22:45:59

MK检验与Morlet小波分析:年降水序列趋势、突变与周期识别方法

从拿到一份几十年的降水资料开始,大家问得最多的就是两个问题:这雨量到底是增了还是减了?变化是不是有规律、存在几年一轮的周期?光看原始曲线,谁也说不清。解决这个问题,水文气象领域用得最顺手的组合就是…

2026/10/15 22:45:59

DDIC显示驱动芯片SPEC解读与选型调试实战指南

简介:这份资源是Synaptics R63455显示驱动芯片的官方Datasheet,面向VR头显显示方案设计、驱动开发与硬件调试的工程师,以及需要查阅DDIC规格细节的研发人员。R63455是一款支持2160/2mux、3240/3mux RGB4320点阵的16777216色液晶控制器驱动&am…

2026/10/15 22:45:59

PF6000拧紧控制器CANopen通信与参数组配置实战指南

简介:本资源是阿特拉斯科普柯(Atlas Copco)Power Focus 6000智能拧紧控制器的官方中文说明书,面向工业自动化产线工程师、设备维护技术人员及智能制造系统集成人员,用于指导设备配置、安全操作与日常维护。文档覆盖从硬…

2026/10/15 22:45:59

Spring核心原理:容器、依赖注入、AOP与事务全解析

1. 先搞清楚Spring到底解决什么问题很多人学Spring,上来就看依赖注入、看AOP、看事务,照着教程能跑通,但一旦报错就完全蒙了。原因很简单:不知道框架底层在做什么,遇到问题只能瞎猜。搞Spring原理,我觉得第…

2026/10/15 22:40:58

Java工程师构建AI Agent的实战路径与架构设计

1. 这不是“转行”,是Java工程师的自然演进路径“13年Java老兵转型AI Agent”——这个标题里最需要被立刻纠正的认知偏差,就是“转型”这个词。我带过不下二十个从传统后端、中间件、金融核心系统走出来的Java开发者,他们做AI Agent项目时&am…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑