c++中如何使用list容器_c++ list双向链表操作指南

必须包含头文件,声明空链表为std::list lst;支持初值列表初始化和范围构造,但不支持size+value构造;仅能用迭代器增删查改,不可用下标访问。

如何正确初始化和声明 std::list

直接用 std::list 前必须包含头文件 ,它不支持随机访问,也不提供 operator[]。常见错误是当成 std::vector 用,比如写 my_list[0] —— 这会编译失败。

  • 声明空链表:std::list lst;
  • 带初值列表初始化(C++11 起):std::list<:string> names = {"Alice", "Bob", "Charlie"};
  • 用另一个容器范围构造:std::list lst(other_vec.begin(), other_vec.end());
  • 注意:不能用 std::list lst(10, 42); 这种“大小+默认值”方式初始化(那是 vector 的语法)

插入和删除操作必须用迭代器,不能用下标

std::list 是双向链表,所有增删都在常数时间完成,但前提是用对接口。误用 push_back()erase() 的迭代器版本以外的方式,容易导致逻辑错乱或性能退化。

  • 头部插入:lst.push_front(10);
  • 尾部插入:lst.push_back("hello");
  • 任意位置插入(在 it 之前):lst.insert(it, 99);
  • 删除单个元素:lst.erase(it);it 必须合法且未失效)
  • 删除满足条件的所有元素:lst.remove_if([](int x) { return x % 2 == 0; });
  • ⚠️ 错误示范:lst.erase(2); —— erase() 没有接受整数索引的重载,这行不通

遍历只能用迭代器或范围 for,不可用下标访问

因为没有 operator[]at(),任何试图“取第 n 个元素”的操作都得靠移动迭代器,时间复杂度是 O(n)。别为了图方便写循环 + std::next(it, n) 来模拟下标——那说明你可能该换容器了。

  • 正向遍历推荐写法:
    for (auto it = lst.begin(); it != lst.end(); ++it) {
        std::cout << *it << "\n";
    }
  • 更简洁的范围 for:
    for (const auto& x : lst) {
        std::cout << x << "\n";
    }
  • 反向遍历:
    for (auto rit = lst.rbegin(); rit != lst.rend(); ++rit) {
        std::cout << *rit << "\n";
    }
  • 想取首/尾元素?用 lst.front() / lst.back(),但务必确保非空,否则未定义行为

list 的 splice、merge、sort 是独有优势,别忽略

这些成员函数不复制元素,只调整指针,是 list 相比其他序列容器的核心价值。尤其 splice() 可以零成本转移节点,常被用于实现任务队列、LRU 缓存等场景。

  • 把另一个 list 的全部内容接过来:lst1.splice(lst1.end(), lst2);(执行后 lst2 为空)
  • 只接某一段:lst1.splice(lst1.begin(), lst2, it1, it2);(左闭右开区间)
  • 合并两个已排序 list:lst1.merge(lst2);lst2 会被清空)
  • 原地排序:lst.sort();(支持自定义比较:lst.sort(std::greater());
  • ⚠️ 注意:sort()merge() 要求元素可比较;splice() 不调用拷贝/移动构造,所以对不可拷贝类型(如 std::unique_ptr)也安全
真正要用好 std::list,关键是放弃“按位置操作”的思维惯性。它的优势不在索引,而在稳定迭代器、高效局部增删、以及零拷贝的节点重组能力。一旦开始频繁调用 std::advance 或手写循环找第 N 个元素,就该重新评估是否选错了容器。