详解C++里面令人迷惑的operator(),std::less等等

前言

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

总结

  1. 只要你重载了operator<,那么你的class就相当于一个Comparableoperator<就相当于java的compareTo
  2. 只要你重载了operator(),那么你的class就相当于一个Comparatoroperator()就相当于java的compare

C++没有接口的概念,重载operator<operator()也不仅仅只有这两种用途。把匿名函数operator()看成java的compare函数实际上是出于一种不成文的规定,使得在std家族里面可以让用户自定义Comparator。不同的语言之间总是在相互借鉴,我们学习的时候也应该做到融汇贯通。


版权声明:本文为u013474815原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。