前言
C和C++里面有非常多令人迷惑的特性,operator()就是其中一个。看完本文之后你会知道,他的本质其实就只是一个匿名函数,C++开发者利用这个畸形的办法实现了类似java里Comparable接口的功能。
第一次见到这个东西的时候是学算法的时候,需要各种调sort()。为了实现逆序排列,我们都会调用一个sort(vec.begin(), vec.end(), std::greater<int>())的东西。
随着题目难度的增加,我们需要对自己定义的class或者struct进行排序,比如对二维点进行排序。这个时候如果想自己定义决定以x坐标作为排序的标准,就头疼了。
我们知道,std::sort()会利用元素自身的小于符号<进行排序。
因此有一种比较简单的解决方法是重载小于符号<:
class MyPoint{
int x, y;
bool operator<(const MyClass& other)const{
return x < other.x;
}
};
注意这两个const不能省略,这是sort对于重载<的要求。
这样就可以进行逆序排列了,下面这个例子是一个类似于HelloWorld的例子:
#include<iostream>
#include<algorithm>
using namespace std;
class MyPoint{
public:
int x,y;
MyPoint(int x, int y){
this->x = x;
this->y = y;
}
bool operator<(const MyPoint& other)const{
return x < other.x;
}
};
int main(){
MyPoint points[] = {MyPoint(2,1), MyPoint(1,2)};
sort(points, points+2);
for(MyPoint& p : points){
cout << p.x << endl;
}
return 0;
}
但是如果此时我们突然想根据x进行逆序排列,或者又根据y进行升序排列,那怎么办呢?
如何将一个函数作为参数
# include <stdio.h>
void foo(){
printf("foo\n");
}
void give_me_name_of_function(void (*funcName)()){
funcName();
}
int main(){
give_me_name_of_function(foo);
return 0;
}
可见,将一个函数作为参数传入的方式是传入一个函数名称。
sort的调用方法之一
在调用sort的时候应该这样:
std::sort(vec.begin(), vec.end(), 返回值为布尔的函数名称);
这里,sort会根据这个函数名称来决定是升序还是逆序排列,类似于java的compare()函数。这个函数的签名必须满足:
bool 返回值为布尔的函数名称(const Type1 &a, const Type2 &b);
如果想实现升序排列,那么应该返回a<b,如果要逆序,就返回a>b。
使用这个办法,我们就可以实现对x坐标的逆序排列了:
#include<iostream>
#include<algorithm>
using namespace std;
class MyPoint{
public:
int x,y;
MyPoint(int x, int y){
this->x = x;
this->y = y;
}
bool operator<(const MyPoint& other)const{
//这两个const不可以省略
return x < other.x;
}
};
bool myCompare(const MyPoint& p1, const MyPoint& p2){
//依据x逆序排列
return p1.x > p2.x;
}
int main(){
MyPoint points[] = {MyPoint(2,1), MyPoint(1,2)};
//利用operator<进行x的升序排列
sort(points, points+2);
cout << points[0].x << endl;
//自定义函数进行x的逆序排列
sort(points, points+2, myCompare);
cout << points[0].x << endl;
return 0;
}
重载operator()
画重点了:重载operator()本质上是定义了一个匿名函数,其效果使得class的instance可以看起来像一个函数
#include<iostream>
using namespace std;
class MyClass{
public:
int x = 0;
MyClass(int x){
this->x = x;
}
void method(int y){
cout << "[method]:" << endl;
cout << "自己: " << x << endl;
cout << "挑战者: " << y << endl;
}
void operator()(int y){
cout << "[匿名函数]:" << endl;
cout << "自己: " << x << endl;
cout << "挑战者: " << y << endl;
}
};
int main(){
MyClass obj(5);
cout << obj.x << endl;
MyClass(5).method(6);
obj.method(6); //直到这里还一切正常
obj(6); //将对象看成一个函数
MyClass(5)(6); //甚至将匿名对象看成一个函数
return 0;
}
重载operator()的实际用途
因为重载operator()可以让obj看上去像一个函数名称,因此sort也被设计成了可以接受instance的模式。在以前,我们将函数名称作为一个参数,现在,我们可以将obj也作为一个参数。
#include<iostream>
#include<algorithm>
using namespace std;
class MyPoint{
public:
int x,y;
MyPoint(int x, int y){
this->x = x;
this->y = y;
}
};
class MyComparator{
public:
int x = 0;
MyComparator(int x){
this->x = x;
}
//类似java的compare函数
bool operator()(MyPoint& left, MyPoint& right){
//不知道为什么这里居然可以省略const了
return left.y < right.y;
}
};
int main(){
MyComparator obj = MyComparator(7);
cout << obj.x << endl;
MyPoint points[] = {MyPoint(1,2), MyPoint(2,1)};
sort(points, points+2, obj);
cout << points[0].x << endl;
return 0;
}
回到std::less<T>
在更多的时候,我们可能会用到std提供的Comparator。正如头文件的名字#include<functional>一样,std::less<T>是一个类的名称,是C++官方给你写好了的一个Comparator,里面的operator()也帮你重载好了的。
std::less<int>()定义了一个匿名对象,同时这个匿名对象看上去非常像正在调用一个函数一样,但是这并不是,它是一个class的Constructor。
这就是为什么你用sort(vec.begin(), vec.end(), std::less<int>())的时候要加括号,而用sort(vec.begin(), vec.end(), myFunctionName)的时候不用加括号的原因了。
反思
回到sort。我们刚才说过在C++标准里面规定了sort传入的用于比较的函数的函数签名必须为:
bool 返回值为布尔的函数名称(const Type1 &a, const Type2 &b);
仔细一想,规定函数的函数签名必须为。。。这不就是java里面的接口吗?
至此,我们再回头来看operator()。如果大家都把operator()的函数签名写成上面的形式,那其实就是Comparator里面的compare函数。
但重载operator()的方式更加灵活,它还能实现各种奇怪的xxxable。
总结
- 只要你重载了
operator<,那么你的class就相当于一个Comparable,operator<就相当于java的compareTo - 只要你重载了
operator(),那么你的class就相当于一个Comparator,operator()就相当于java的compare
C++没有接口的概念,重载operator<和operator()也不仅仅只有这两种用途。把匿名函数operator()看成java的compare函数实际上是出于一种不成文的规定,使得在std家族里面可以让用户自定义Comparator。不同的语言之间总是在相互借鉴,我们学习的时候也应该做到融汇贯通。