std::sort配合函数指针或lambda实现自定义排序:函数指针轻量但无捕获能力,lambda更灵活但需防悬垂引用;比较函数须满足严格弱序,多字段排序用短路表达式;避免qsort及裸指针失效风险。

用 std::sort 配合函数指针或 lambda 实现自定义排序
直接传函数指针给 std::sort 是最轻量的方式,适合逻辑稳定、不捕获外部变量的场景。C++11 起更推荐用 lambda,它能隐式捕获局部变量,写法紧凑。
常见错误是把比较函数写成「大于」逻辑却当成默认升序用,结果顺序全反;或者 lambda 捕获了栈上已销毁的对象(比如在循环里存了指向临时 std::string 的指针),导致运行时崩溃。
-
std::sort要求比较函数返回bool,且必须满足严格弱序:对任意a、b、c,若comp(a,b)和comp(b,c)为真,则comp(a,c)也必须为真;同时comp(a,a)必须为假 - 若需按多个字段排序(如先按
score降序,再按name升序),别嵌套if,用短路表达式:[&](const auto& x, const auto& y) { return x.score != y.score ? x.score > y.score : x.name - 函数指针方式要显式声明类型,比如
bool (*cmp)(const Person&, const Person&) = [](const Person& a, const Person& b) { return a.id ,再传给 <code>std::sort(v.begin(), v.end(), cmp)
用指针数组 + 自定义比较器绕过对象拷贝开销
当排序对象很大(如含 std::vector 或 std::string 成员),又不想修改原容器顺序时,建一个指针数组(std::vector<const t></const> 或 std::vector<t></t>)来间接排序,避免移动/拷贝本体。
关键点在于比较器必须解引用指针后再比,否则比的是地址值;而且要注意原始数据生命周期——如果原 std::vector 在排序后被 clear() 或 resize(),指针就悬空了。
立即学习“C++免费学习笔记(深入)”;
- 初始化指针数组:
std::vector<const item> ptrs; ptrs.reserve(items.size()); for (const auto& item : items) ptrs.push_back(&item);</const> - 比较器写成:
[&](const Item* a, const Item* b) { return a->priority > b->priority; }(注意是a->priority,不是*a.priority) - 排序后遍历
ptrs即得有序访问序列,items本身完全不动
手写快排时用 void* + 函数指针模拟泛型(慎用)
仅在不能用模板、又需要一套排序逻辑复用到不同结构体时才考虑。C 风格的 qsort 就是典型例子,但 C++ 中几乎没理由选它——类型不安全,编译期无检查,性能还常不如 std::sort。
若真要仿写,核心是把比较函数做成 int (*)(const void*, const void*) 类型,内部强制转换指针并取字段。最容易出错的是字节对齐和结构体内存布局差异,比如 #pragma pack(1) 下的结构体传给默认对齐的比较器会读错字段。
- 不要直接对
std::vector的data()传给qsort排std::string——std::string不是 POD,qsort会破坏其内部指针 - 若必须用,确保类型是 trivially copyable:可用
static_assert(std::is_trivially_copyable_v<t>)</t>检查 - 比较函数里做转换前,先确认指针非空,否则
qsort可能在边界处传入非法地址
迭代器失效与指针有效性必须同步管理
排序本身不会让指针失效,但如果你排序的是 std::vector,而之前保存了其中元素的裸指针,那么 std::sort 过程中元素位置会变,那些指针仍指向原地址——只是该地址现在存着别的元素了。这不属于迭代器失效,但效果一样危险。
真正麻烦的是 std::list 或 std::deque:前者排序不移动元素(只改节点指针),裸指针依然有效;后者可能因内存分段导致指针突然无效。所以「用指针排序」的前提,是明确知道底层数组是否重排、以及你持有的指针是否还映射到正确语义。
- 安全做法:排序前用
std::addressof(*it)取地址,而非&*it(后者对代理迭代器如std::vector<bool>::iterator</bool>会崩) - 若原始容器可能增删,优先用索引或智能指针(如
std::shared_ptr)代替裸指针 - 调试时加断点观察排序前后指针值变化,比靠经验判断更可靠
指针参与排序真正的复杂点不在语法,而在所有权和生命周期的耦合——你得同时盯住三件事:比较逻辑是否满足数学要求、指针所指内存是否持续有效、排序操作是否意外改变了你依赖的物理布局。


















