各种排序问题中比较器的写法¶
排序实际上就是不停地交换元素,来达到我们想要的升序或降序结果,而比较器实际上就是我们自己在定义什么时候需要交换两个元素,当其返回为假时不交换,返回真时交换,即把后一个元素跟前一个元素交换位置
以此题中qsort的比较器为例:
服务器有一批任务需要处理。每个任务有
ID、优先级(priority) 和耗时(cost)。 定义结构体Task。规则:
- 优先级高的任务先执行(数值越大优先级越高)。
- 优先级相同,耗时短的先执行。
- 如果都相同,ID小的先执行。
其比较器可以写为:
typedef struct {
int id;
int priority;
int cost;
}Task;
int cmp(const void *a,const void *b)
{
Task*t1=(Task*)a;
Task*t2=(Task*)b;
if(t1->priority!=t2->priority)
{
return t1->priority<t2->priority;
}
if(t1->cost!=t2->cost)
{
return t1->cost>t2->cost;
}
return t1->id>t2->id;
}
当返回的值为真时,相当于把b放到a的前面(交换了a和b) 返回值为假时,不改变a,b顺序(不交换a和b)
比如1,5 想要它为降序,则要交换a和b,需要返回值为真
而当return a<b时,返回值为真,qsort(交换了a和b)把5放在1前面,就实现了降序
不妨再看看优先队列中比较器的写法:
这里我想实现升序排列,思想与上述相同,确定什么时候需要交换两个元素,写出相关的返回值的表达式即可
问题又来了,为什么这里的compare比较器必须写成这种"operator()"的函数名呢,写成随便一个其他的函数名不行吗?
这是因为(from GPT 5.2) 我个人理解为,其主要是因为编译器只能识别operator这种关键字,才能把它当作一个比较器对象来处理
一、cmp 在这里“扮演的角色”是什么?¶
在这行代码里:
第三个模板参数 cmp 的语义要求是:
一个“比较器类型”,它的对象必须能被“调用” 即:给我两个
ListNode*,我能返回一个bool
也就是说,priority_queue 内部会写类似这样的代码(伪代码):
👉 注意这里:comp(a, b)
这要求 comp 必须像函数一样被调用。
二、C++ 中:什么东西能“像函数一样调用”?¶
在 C++ 里,能写成:
只有三种情况:
- 函数
- 函数指针
- 重载了
operator()的对象
而这里传给 priority_queue 的是:
所以唯一合法的方式就是:
👉 让 cmp 成为“函数对象(functor)”
三、什么是函数对象(Functor)?¶
一个类 重载了 operator() 之后:
就可以这样用:
这正是 priority_queue 想要的东西。
四、为什么不能叫别的名字?比如 compare()?¶
如果你写成:
那你只能这样调用:
❌ 但是 priority_queue 不会这么写,它只会写:
👉 编译器根本不知道你有个 compare()。
五、operator() 是“语法层面的约定”,不是 STL 特有¶
这一点非常重要:
operator()不是priority_queue发明的 它是 C++ 语言层面定义的“函数调用运算符”
只要你写了:
编译器就会自动翻译为:
六、类比(非常形象)¶
你可以这样理解:
operator+→ 让对象能写a + boperator[]→ 让对象能写a[i]operator()→ 让对象能写a(x, y)
priority_queue 只认 a(x, y) 这种“函数式调用”。
七、那为什么 STL 不直接用函数指针?¶
可以,但有三个问题:
- 函数指针不能保存状态
- 性能略差(不能内联)
- 模板无法在编译期充分优化
而函数对象(带 operator()):
- 可以内联
- 可以携带成员变量
- 是 STL 的核心设计思想
八、总结一句话(考试 / 面试级答案)¶
priority_queue要求比较器是一个可调用对象, 在 C++ 中,使一个对象可调用的唯一方式就是重载operator(), 因此比较器类必须提供operator()。