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

C++ 中 std::list使用详解和实战示例

1. 定义与特性

  • std::list双向链表(doubly linked list)的封装。

  • 所有元素在内存中不是连续存储的,每个节点保存元素值及前后指针。

  • 特点:

    • 插入、删除 任意位置元素 的时间复杂度是 O(1)(前提是已知位置的迭代器)。
    • 不支持随机访问(没有 operator[])。
    • 迭代器是 双向迭代器(bidirectional iterator),不能做 it + n 这样的操作。

2. 头文件与定义

#include <list>
#include <iostream>
using namespace std;int main() {list<int> lst;           // 空链表list<int> lst2(5, 10);   // 含 5 个元素,值为 10list<int> lst3 = {1,2,3,4}; // 列表初始化
}

3. 迭代器类型

  • begin() / end():正向迭代器
  • rbegin() / rend():反向迭代器
  • cbegin() / cend():常量迭代器
list<int> lst = {1, 2, 3};
for (auto it = lst.begin(); it != lst.end(); ++it) {cout << *it << " ";
}

4. 常用接口

构造与赋值

list<int> a;                  // 空
list<int> b(5, 1);            // 5 个 1
list<int> c(b);               // 拷贝
list<int> d = {1,2,3,4};      // 初始化列表
a = d;                        // 赋值

插入与删除

list<int> lst = {1, 2, 3};// 头尾操作
lst.push_back(4);    // 尾部插入
lst.push_front(0);   // 头部插入
lst.pop_back();      // 尾部删除
lst.pop_front();     // 头部删除// 任意位置
auto it = lst.begin();
++it;                // 指向第二个元素
lst.insert(it, 99);  // 在 it 前插入 99
lst.erase(it);       // 删除 it 指向的元素lst.clear();         // 清空

容量

lst.size();     // 元素个数
lst.empty();    // 是否为空
lst.max_size(); // 理论最大容量

访问

std::list 不支持随机访问,没有 operator[]。只能通过迭代器或 front() / back()

cout << lst.front(); // 第一个元素
cout << lst.back();  // 最后一个元素

5. std::list 独有接口

这些操作利用链表特性,效率很高:

list<int> a = {1, 3, 5};
list<int> b = {2, 4, 6};a.merge(b);    // 归并 b 到 a(要求已排序)
a.splice(a.begin(), b);  // 把 b 的元素整体移动到 a 的开头
a.remove(3);   // 删除所有值为 3 的元素
a.remove_if([](int x){ return x % 2 == 0; }); // 删除偶数
a.reverse();   // 反转链表
a.unique();    // 删除相邻重复元素(要求已排序)
a.sort();      // 内部排序(链表版归并排序)

6. 时间复杂度总结

  • 插入/删除(已知迭代器位置):O(1)
  • 查找第 k 个元素:O(n)(只能顺序走迭代器)
  • 访问头尾元素:O(1)
  • 排序:O(n log n)(链表版归并排序)
  • 遍历:O(n)

7. 与 vectordeque 对比

特性vectordequelist
底层结构连续数组分段连续数组双向链表
随机访问O(1)O(1)不支持
插入删除效率尾部 O(1),中间 O(n)头尾 O(1),中间 O(n)任意位置 O(1)(已知迭代器)
内存局部性一般
排序方式std::sort(快排/堆排)std::sort内置 list::sort()(归并排序)

总结

  • 如果你需要 频繁在任意位置插入/删除 → 用 list
  • 如果需要 高效随机访问 → 用 vector
  • 如果需要 两端操作 + 随机访问 → 用 deque

8. 使用注意事项

  1. 迭代器稳定性

    • list 来说,插入、删除不会使其他元素的迭代器失效(只会失效指向被删除的元素)。
    • 这是区别于 vector 的大优势。
  2. 内存开销

    • 每个节点都需要额外存储两个指针,开销比 vector 大。
  3. 遍历性能

    • 因为不连续,CPU cache 利用率差,遍历速度一般比 vector 慢很多

9、std::list 节点插入 / 删除过程演示

1. 节点结构

std::list 中,每个元素存储在一个节点里,节点是 双向链表

+-------------------+
|  value (数据)     |
|  prev 指针        | ---> 指向前一个节点
|  next 指针        | ---> 指向后一个节点
+-------------------+

所有节点通过 prevnext 串起来,形成双向链表。
通常 STL 的实现会有一个 哨兵节点 (sentinel / dummy node),让 begin() / end() 更统一。


2. 初始链表

假设我们有一个 std::list<int>,存了 [10, 20, 30]

 head → [10] ⇆ [20] ⇆ [30] ← tail

更细致的图( 表示双向连接):

nullptr ← [10] ⇆ [20] ⇆ [30] → nullptr

3. 插入节点

我们想在 20 前插入一个新元素 15

插入前:

[10] ⇆ [20] ⇆ [30]

插入过程:

  1. 分配一个新节点 15

  2. 改变指针关系:

    • 15.prev = 10
    • 15.next = 20
    • 10.next = 15
    • 20.prev = 15

插入后:

[10] ⇆ [15] ⇆ [20] ⇆ [30]

注意:只改动 常数个指针,所以插入复杂度 O(1)
vector 插入时可能需要 移动大量元素


4. 删除节点

我们删除元素 20

删除前:

[10] ⇆ [15] ⇆ [20] ⇆ [30]

删除过程:

  1. 改变指针关系:

    • 15.next = 30
    • 30.prev = 15
  2. 释放 20 节点内存。

删除后:

[10] ⇆ [15] ⇆ [30]

删除同样只修改 常数个指针,复杂度 O(1)


5. 内存对比:vector vs list

vector

连续存储:

[10][15][20][30][40]...
  • 插入 / 删除中间元素 → 需要 搬移后续元素
  • 优点:cache 友好,随机访问 O(1)。

list

分散存储:

[10] ⇆ [15] ⇆ [20] ⇆ [30]
  • 插入 / 删除只改指针。
  • 缺点:不支持随机访问,遍历性能差于 vector

6. 图示总结

  • 插入节点时:只需改 4 条指针
  • 删除节点时:只需改 2 条指针 + 释放内存
  • 这就是 list 在频繁插入 / 删除时比 vector 高效的原因。

10. 示例程序

#include <iostream>
#include <list>
using namespace std;int main() {list<int> lst = {5, 2, 3, 2, 4};cout << "初始链表: ";for (int x : lst) cout << x << " ";cout << endl;lst.push_front(1);lst.push_back(6);cout << "插入后: ";for (int x : lst) cout << x << " ";cout << endl;lst.remove(2); // 删除所有值为 2cout << "删除值 2 后: ";for (int x : lst) cout << x << " ";cout << endl;lst.sort();cout << "排序后: ";for (int x : lst) cout << x << " ";cout << endl;lst.reverse();cout << "反转后: ";for (int x : lst) cout << x << " ";cout << endl;
}

11. 实战应用示例-任务调度队列

背景

在 SLAM 或点云处理系统中,我们经常要维护一个 任务队列

  • 每个任务有不同的优先级(高优先任务要尽快处理)。
  • 任务可能在运行时随时被取消 / 插入。
  • 任务完成后要从队列中移除。

这种情况下:

  • 随机访问需求很少(不需要按索引访问第 k 个任务)。
  • 频繁插入 / 删除(尤其是中间位置)。
    这就是 std::list 的优势场景。

示例代码

#include <iostream>
#include <list>
#include <string>struct Task {int id;int priority;   // 数字越大,优先级越高std::string name;
};// 打印任务队列
void printTasks(const std::list<Task>& tasks) {for (auto& t : tasks) {std::cout << "[ID=" << t.id << ", P=" << t.priority << ", " << t.name << "]  ";}std::cout << "\n";
}int main() {std::list<Task> taskQueue;// 插入一些任务(模拟实时到来)taskQueue.push_back({1, 1, "Mapping"});taskQueue.push_back({2, 3, "LoopClosure"});taskQueue.push_back({3, 2, "Optimization"});std::cout << "初始任务队列:\n";printTasks(taskQueue);// ===== 根据优先级插入新任务 =====Task newTask = {4, 5, "HighPriorityLocalization"};auto it = taskQueue.begin();// 找到插入位置(保持优先级降序)while (it != taskQueue.end() && it->priority >= newTask.priority) {++it;}taskQueue.insert(it, newTask);std::cout << "\n插入高优先级任务后:\n";printTasks(taskQueue);// ===== 删除任务(任务完成) =====for (auto it = taskQueue.begin(); it != taskQueue.end(); ) {if (it->id == 2) {  // 删除 LoopClosure 任务it = taskQueue.erase(it);} else {++it;}}std::cout << "\n删除任务 ID=2 后:\n";printTasks(taskQueue);// ===== 动态调度(修改任务优先级 -> 移动位置) =====for (auto it = taskQueue.begin(); it != taskQueue.end(); ) {if (it->id == 1) {Task updated = *it;updated.priority = 6;  // 提升优先级it = taskQueue.erase(it); // 删除旧位置// 重新插入合适位置auto pos = taskQueue.begin();while (pos != taskQueue.end() && pos->priority >= updated.priority) {++pos;}taskQueue.insert(pos, updated);} else {++it;}}std::cout << "\n任务 ID=1 提升优先级后:\n";printTasks(taskQueue);return 0;
}

输出示例

初始任务队列:
[ID=1, P=1, Mapping]  [ID=2, P=3, LoopClosure]  [ID=3, P=2, Optimization]  插入高优先级任务后:
[ID=4, P=5, HighPriorityLocalization]  [ID=2, P=3, LoopClosure]  [ID=3, P=2, Optimization]  [ID=1, P=1, Mapping]  删除任务 ID=2 后:
[ID=4, P=5, HighPriorityLocalization]  [ID=3, P=2, Optimization]  [ID=1, P=1, Mapping]  任务 ID=1 提升优先级后:
[ID=1, P=6, Mapping]  [ID=4, P=5, HighPriorityLocalization]  [ID=3, P=2, Optimization]

为什么用 std::list

  • 插入任务 → 只需找到位置然后 O(1) 修改指针。

  • 删除任务 → 直接用迭代器 erase(),O(1)。

  • 修改优先级 → 先 erase(),再 insert(),同样 O(1)。

  • 如果换成 vector

    • 插入 / 删除会导致元素移动,最坏 O(n)。
    • 任务数量大时会严重影响实时性。

真实项目中的应用场景

  • SLAM 后端优化任务调度:前端关键帧插入 → 优先放高优先任务(回环检测)。
  • 点云处理流水线:需要随时插入 / 取消滤波任务。
  • 网络/分布式系统:消息队列(部分场景 std::deque 也可)。
  • 游戏引擎:实体的事件调度(插入/删除频繁)。

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

相关文章:

  • IO流的简单介绍
  • 【AI论文】SAIL-VL2技术报告
  • 基于 SSM(Spring+SpingMVC+Mybatis)+MySQL 实现(Web)软件测试用例在线评判系统
  • 【2/20】理解 JavaScript 框架的作用:Vue 在用户界面中的应用,实现一个动态表单应用
  • Android冷启动和热启动以及温启动都是什么意思
  • Java数据结构 - 单链表的模拟实现
  • git忽略CRLF警告
  • 不用手也能玩手机?多代理协作框架让 APP 自动执行任务
  • 装备制造企业支撑智能制造的全生命周期数据治理实践
  • 【论文阅读 | AAAI 2025 | Mamba YOLO: 基于状态空间模型的目标检测简单基线​​】
  • AdMergeX与小川科技最右App合作案例入选中国信通院“高质量数字化转型典型案例集”
  • LoadBalancer配置
  • 国内外主流开源密码库:演进背景、国密适配与企业维护挑战
  • 车规级MCU在特种车辆车身控制中的应用研究
  • 深度学习基本模块:GRU 门控循环单元
  • 通过Keepalived+LVS搭建NAT模式的高可用集群系统保姆级教程
  • 设备硬件能力调用:传感器、蓝牙与定位
  • 完全二叉树的链式创建以及遍历
  • 数据结构——二叉树和BST(2)
  • 一文解码百度地图ETA
  • 好题推荐-剑指S10
  • Python 中的魔术方法(Magic Methods)
  • JavaScript事件循环机制----event loop
  • C++编程学习(第33天)
  • 伺服上位机展示
  • Class62 优化算法
  • 电气专业科研怎么入门?电气仿真入门秘籍
  • 软考-系统架构设计师 基于构件的软件工程详细讲解
  • MCP 项目标准管理工具 v1.1.0 发布:新增 API 调试功能,助力 AI 辅助开发标准化
  • Linear Algebra in Competitive Programming