在 C++中,重载操作符 <
和重载函数调用操作符 ()
各自适用于不同的情况,它们的使用取决于你的具体需求。
比较 < 和 ()
重载操作符 <
- 排序和比较: 当你需要定义一个类或结构体的对象如何进行排序或比较时,你会重载操作符
<
。这在使用标准库中的排序函数(如std::sort
)、集合(如std::set
)或其他需要进行元素比较的算法时特别有用。通过重载<
,你可以指定对象比较的准则,让它们按照特定的顺序排列。
重载函数调用操作符 ()
(也称为仿函数或函数对象)
- 自定义行为的函数调用: 当你希望一个对象的实例能够像函数一样被调用时,你会重载函数调用操作符
()
。这使得对象可以存储状态,同时根据这些状态执行特定的操作。重载()
的类实例通常被称为仿函数(function objects)或函数对象。 - 用于算法的自定义操作: 在标准库算法(如
std::sort
、std::find_if
等)中,你可以传递函数对象作为参数,用来定义算法的具体行为。这些函数对象可以比普通函数或 lambda 表达式携带更多的信息,因为它们可以有成员变量。 - 回调函数和策略模式: 仿函数也常用于实现回调函数和策略模式,允许在运行时选择算法的行为。
示例场景
假设你有一个 Student
类,需要根据学生的成绩排序。
- 重载
<
: 你会重载<
来定义两个Student
对象如何比较,通常是基于它们的成绩。 - 重载
()
: 如果你想要在排序时根据不同的标准(如年龄、姓名等)动态选择排序准则,你可以定义一个仿函数,它接受两个Student
对象,内部根据当前的需求来比较这两个对象。
结论
选择重载 <
还是 ()
取决于你的具体需求:如果需要定义对象的默认排序或比较规则,那么重载 <
是合适的。如果你需要更多的灵活性或者想要一个对象表现得像一个可以携带状态的函数,那么重载 ()
会是更好的选择。
以 [[重载比较操作符]] 中的代码为例
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 9;
struct Book
{
int a, b, c;
bool operator<(const Book &x) const
{
if (a != x.a)
return a < x.a;
if (b != x.b)
return b < x.b;
return c < x.c;
}
} p[N];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> p[i].a >> p[i].b >> p[i].c;
sort(p + 1, p + n + 1);
for (int i = 1; i <= n; ++i)
cout << p[i].a << " " << p[i].b << " " << p[i].c << "\n";
return 0;
}
这段代码可以被修改成
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 9;
struct Book
{
int a, b, c;
} p[N];
struct cmp{
bool operator() (const Book &x, const Book &y) const {
if (x.a != y.a)
return x.a < y.a;
if (x.b != y.b)
return x.b < y.b;
return x.c < y.c;
}
};
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> p[i].a >> p[i].b >> p[i].c;
sort(p + 1, p + n + 1, cmp());// cmp() 创建匿名对象,调用对象中的方法
for (int i = 1; i <= n; ++i)
cout << p[i].a << " " << p[i].b << " " << p[i].c << "\n";
return 0;
}
上述代码中注释部分的解释
在 C++中,当你向 std::sort
(或类似的标准库算法)传递一个比较函数或对象时,传递方式取决于比较逻辑是通过函数还是通过对象实现的。
使用比较函数
如果比较逻辑是以自由函数(或静态成员函数)的形式实现的,你直接传递该函数的名称(不带括号)。这是因为你提供的是函数指针,指向那个实现比较逻辑的函数。
bool compareFunction(const Type& a, const Type& b) {
// 实现比较逻辑
}
std::sort(arr, arr + n, compareFunction); // 直接传递函数名
使用比较对象
当比较逻辑是通过重载 operator()
的类或结构体(即仿函数或函数对象)实现的,你需要传递该类型的实例。cmp()
在这里是创建 cmp
类型的一个临时对象,这个对象被用作比较的依据。括号是构造函数的调用操作,哪怕该构造函数没有参数,括号也是必须的,以表明你正在构造一个对象。
struct Compare {
bool operator()(const Type& a, const Type& b) const {
// 实现比较逻辑
}
};
std::sort(arr, arr + n, Compare()); // 创建Compare类型的临时对象
因此,当你看到 std::sort(arr, arr + n, cmp());
这样的调用时,cmp()
表示的是创建一个 cmp
类型的临时对象,而非调用函数。这个对象将会被用作排序算法中的比较基准。如果 cmp
是一个结构体或类,其中重载了 operator()
,这样做是完全正确的,且是必需的,因为标准库算法需要一个可以调用的实体来进行元素间的比较。
关于类似 set<int,cmp>的解释
在您提供的代码中,cmp
是用作 multiset
的自定义排序准则。当您这样声明一个 multiset
时:
multiset<int, cmp> st;
您实际上是在告诉 multiset
使用 cmp
作为其元素比较的规则。这里的关键在于 multiset
的模板参数。multiset
是一个模板类,它接受两个参数:元素类型和比较类型。当您指定 cmp
作为第二个模板参数时,您实际上是告诉编译器:“我要使用 cmp
的实例作为比较对象。”
这种情况下,不需要在 cmp
后面加括号,因为您不是在创建一个 cmp
类型的对象实例,而是在指定一个类型。编译器知道如何为 multiset
的内部机制实例化这个比较对象。当 multiset
需要比较其内部元素时,它会自动使用 cmp
类型的一个隐式实例进行比较。
简而言之,在 multiset<int, cmp> st;
这行代码中,cmp
是作为一个类型传递给 multiset
的,所以不需要括号。括号用于创建类型的实例,而在模板参数中,您只是在指定类型本身,实际的实例化过程是自动发生的,无需显式创建实例。