网站首页 全球最实用的IT互联网站!

人工智能P2P分享Wind搜索发布信息网站地图标签大全

当前位置:诺佳网 > 软件工程 > 后端开发 > C++ >

C++ 中的 list

时间:2026-01-03 23:02

人气:

作者:admin

标签:

导读:深入理解 C 中的 std::list 双向链表容器,探讨其底层原理、独有优势(如头部操作、接合等)、迭代器特性,以及与 std::vector 的选择权衡。...

目录

本文首发于我的个人博客:Better Mistakes

版权声明:本文为原创文章,转载请附上原文出处链接及本声明。
由于技术迭代较快,文章内容可能随时更新(含勘误及补充)。为了确保您看到的是最新版本,并获得更好的代码阅读体验,请访问:

???? 原文链接https://bfmhno3.github.io/note/list-in-cpp/


在现代 C++ 开发中,虽然 std::vector 足以应付绝大多数的场景,但是在某些特定场景下,std::list 依旧是不可替代的神器。

核心概念与底层原理

  • 头文件:#include <list>
  • 本质:双向链表(Doubly Linked List)
  • 内存模型:非连续内存。每个元素(节点)都是独立分配在堆上的,节点之间通过指针(prevnext)连接
  • 特点:
    • 不支持随机访问:不能使用下标 l[5] 访问元素,必须从头一个一个遍历过去
    • 插入 / 删除极快:只要持有了某个位置的迭代器,在该位置插入或删除元素的操作是 \(O(1)\) 的,且不需要移动其他元素

初始化与构造

用法与 std::vector 非常相似:

#include <list>

std::list<int> l1;              // 空链表
std::list<int> l2 = {1, 2, 3};  // 列表初始化
std::list<int> l3(l2);          // 拷贝构造
std::list<int> l4(5, 100);      // 5 个 元素,全是 100

独有的操作优势(std::vector 做不到的)

这是 std::list 存在的理由。由于它是链表,它支持一些操作极其高效,或者提供了 std::vector 根本没有的接口。

头部操作

std::vector 在头部插入 / 删除操作极其低效(\(O(N)\)),而 std::list 则是瞬间完成(\(O(1)\))。

  • push_front(val):头部插入
  • pop_front():头部删除

接合(Splicing)

可以将一个 std::list 的元素(全部或部分)直接 “剪切” 并 “粘贴” 到另一个 std::list 中,这是纯指针操作,没有数据拷贝,所以速度极快。

std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};

auto it =  list1.begin();
++it; // 指向 2

// 将 list2 的所有元素 “移动” 到 list1 的 2 前面
// list2 变空,list1 变为 {1, 4, 5, 6, 2, 3}
list1.splice(it, list2);

专用成员函数

由于 std::list 无法随机访问,标准库算法(如 std::sort)对它无效。因此 list 自带了一套经过优化的成员函数:

  • l.sort():排序(稳定排序,底层通常是归并排序)。注意:千万别读 std::liststd::sort(l.begin(), l.end()),编译会报错
  • l.remove(val):删除所有等于 val 的元素
  • l.remove_if(pred):删除满足条件的元素
  • l.unique():删除相邻的重复元素(去重前通常要先 sort)
  • l.reverse():逆置链表
  • l.merge(l2):合并两个已排序的链表(l2 会变空)

迭代器特性

这是决定选择 std::vector 还是 std::list 的关键因素。

  • 类型:双向迭代器Bidirectional Iterator
    • 支持:++it--it*it
    • 不支持:it + 5it < other_it(不能跳跃,不能比较大小)
  • 稳定性(Validity):极高
    • 掺入操作:绝对不会让现有的迭代器失效
    • 删除操作:只有指向被删除节点的那个迭代器会失效,其他的完全不受影响
    • 对比 std::vectorstd::vector 一旦扩容,所有迭代器全部失效;中间插入,后面所有迭代器失效。

std::liststd::vector 的选择

这是一个经典的 Trade-off(权衡)问题:

特性 std::vector std::list
随机访问 \(O(1)\)(支持 [] 不支持(\(O(N)\)
尾部插入 \(O(1)\) \(O(1)\)
头部/中间插入 \(O(N)\)(很慢,要挪动数据) \(O(1)\)(很快,前提是有迭代器)
内存布局 连续(Cache 命中率高) 分散(Cache 命中率低)
额外内存 较少(少量预留空间) 较大(每个元素都要存两个指针)
迭代器失效 容易失效 几乎不失效

结论:

  • 95% 的情况用 std::vector:现代 CPU 的缓存机制非常依赖内存连续性。即使是在中间插入,对于小对象(如 int),std::vector 往往也比 std::list 快,因为 std::list 的遍历会导致大量的 Cache Miss。
  • 以下情况可以用 std::list
    1. 需要频繁在头部或中间插入 / 删除,且元素总数很多
    2. 对象非常大,拷贝代价极高(虽然 C++11 移动语义缓解了这个问题)
    3. 核心需求:你需要保存指向某个元素的迭代器,并且在后续操作中(无论怎么插入删除其他元素),希望这个迭代器一直有效

C++11 新增:std::forword_list

C++11 引入了 std::forword_list(单向链表):

  • 特点:只有 next 指针,没有 prev 指针
  • 优势:比 std::list 更省内存(少存一个指针)
  • 劣势:只能向前遍历,功能受限
  • 场景:极其追求内存优化的哈希表桶实现等

???? 写在最后

如果你觉得这篇文章对你有帮助,欢迎到我的个人博客 Better Mistakes 逛逛。

在那里我归档了更多高质量的技术文章,也欢迎通过 RSS 订阅我的最新动态!

上一篇:

下一篇:

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信