std::list 的 sort() 必须调用成员函数而非 std::sort,因其不支持随机访问迭代器;成员版用归并排序,稳定、原地、o(n log n);排序后迭代器仍有效但位置重排,地址不变;性能上常慢于 vector+std::sort。

std::list 的 sort() 成员函数必须用成员版,不能用 std::sort
因为 std::list 是双向链表,不支持随机访问迭代器,而 std::sort 要求 RandomAccessIterator —— 直接传 list.begin() 和 list.end() 会编译失败,常见错误信息是:error: no match for 'operator-' 或类似“invalid operands to binary expression”。
正确做法是调用 list.sort() 成员函数,它内部用归并排序实现,时间复杂度 O(n log n),且稳定、原地、无需额外随机访问能力。
- 升序:直接调用
my_list.sort() - 降序:传
std::greater<int>()</int>或自定义比较器,如my_list.sort(std::greater<int>())</int> - 自定义类型:传 lambda(C++14+)或仿函数,注意捕获需谨慎,lambda 不能有状态(除非用可变 lambda + 引用捕获,但易出错)
自定义比较逻辑时,lambda 必须是 const 且无副作用
std::list::sort() 会多次调用比较函数,且不保证调用顺序或次数。如果 lambda 捕获了局部变量并修改它(比如计数器),行为未定义;更常见的是误用非 const 引用捕获导致编译失败。
典型安全写法:
立即学习“C++免费学习笔记(深入)”;
std::list<Person> people = {/* ... */};
people.sort([](const Person& a, const Person& b) {
return a.age < b.age; // ✅ 只读访问,无捕获
});
- 避免捕获局部变量,尤其不要用
[&]——sort()可能在不同线程或重排后调用,引用可能已失效 - 如果真要按外部数据排序(如按某 vector 索引),把索引数据复制进 lambda 捕获列表,用
[indices = std::move(indices)](C++14+) - 成员函数指针也可用,如
people.sort(&Person::operator,但要求 <code>operator 存在且语义合理
排序后迭代器仍有效,但元素位置全变 —— 别依赖原地址
std::list::sort() 是原地重排节点指针,所有迭代器、引用、指针仍指向原对象(内存地址不变),这点和 vector 排序完全不同。看起来很友好,但容易误判。
- 如果你存了某个元素的
iterator,排序后它仍有效,但不再代表“第几个”——位置完全打乱 - 别在排序前保存
&*it去比地址,以为能定位“原来那个”,这没意义;list里每个节点独立分配,地址本就不连续 - 若需保持某种顺序映射(如 ID → 当前排名),排序后必须重新遍历生成新映射,不能靠旧迭代器算偏移
性能敏感场景下,考虑是否真该用 list
很多人选 std::list 是冲着“插入删除快”,但排序本身开销不小:虽然算法是 O(n log n),但链表节点分散在堆上,缓存不友好,实测常比 std::vector + std::sort 慢 2–5 倍(尤其小对象)。
- 如果数据量小(list 合理
- 如果排序频繁、或元素小(如 int、short)、或后续还要随机访问,优先用
vector+sort,再用stable_sort保序 -
list.splice()配合手动归并,理论上更快,但实现复杂、易错,几乎没人这么干
真正需要 list::sort() 的典型场景只剩一个:你已经在用 list 做大量中间插入/删除,且必须维持排序,又不能接受拷贝或 move 构造开销(比如大对象、不可移动类型)。











