我确实研究了仿函数的整个想法,遗憾的是我无法理解仿函数相对于典型函数的真正优势。
根据一些学术脚本,仿函数可以保持状态不同于函数。 任何人都可以用一些简单易懂的例子来详细说明这个吗?
我真的无法理解为什么典型的常规功能无法做到这一点。对于这种新手问题,我真的很抱歉。
答案 0 :(得分:3)
作为一个非常简单的演示,让我们考虑快速排序。我们选择一个值(通常称为" pivot")并将输入集合分成比较小于枢轴的那些,以及那些比较大于或等于pivot 1
标准库已经有std::partition
可以自行进行分区 - 将一个集合分成满足指定条件的项目和那些不需要的项目。因此,要进行分区,我们只需提供合适的谓词。
在这种情况下,我们需要一个简单的比较:return x < pivot;
。然而,每次传递枢轴值变得困难。 std::partition
只是从集合中传递一个值并询问:&#34;这是否通过了您的测试?&#34;您无法告诉std::partition
当前的数据透视值是什么,并在调用时将其传递给您的例行程序。当然可以完成(例如,Windows中的许多枚举函数以这种方式工作),但它变得非常笨拙。
当我们调用std::partition
时,我们已经选择了透视值。我们想要的是一种方法...将该值绑定到将传递给比较函数的参数之一。一个非常丑陋的方法就是&#34;传递&#34;它通过一个全局变量:
int pivot;
bool pred(int x) { return x < pivot; }
void quick_sort(int *begin, int *end) {
if (end - begin < 2)
return;
pivot = choose_pivot(begin, end);
int *pos = std::partition(begin, end, pred);
quick_sort(begin, pos);
quick_sort(pos, end);
}
我真的希望我不必指出,如果我们可以帮助它,我们宁愿不使用全局。避免它的一种相当简单的方法是创建一个函数对象。我们在创建对象时传递当前的数据透视值,并将该值作为状态存储在对象中:
class pred {
int pivot;
public:
pred(int pivot) : pivot(pivot) {}
bool operator()(int x) { return x < pivot; }
};
void quick_sort(int *begin, int *end) {
if (end-begin < 2)
return;
int pivot = choose_pivot(begin, end);
int *pos = std::partition(begin, end, pred(pivot));
quick_sort(begin, pos);
quick_sort(pos, end);
}
这增加了一些额外的代码,但作为交换,我们已经消除了全球 - 一个相当合理的交换。
当然,从C ++ 11开始,我们可以做得更好 - 添加语言&#34; lambda表达式&#34;这可以创建一个类似于我们的类。使用它,我们的代码看起来像这样:
void quick_sort(int *begin, int *end) {
if (end-begin < 2)
return;
int pivot = find_pivot(begin, end);
auto pos = std::partition(begin, end, [pivot](int x) { return x < pivot; });
quick_sort(begin, pos);
quick_sort(pos, end);
}
这改变了我们用来指定类/创建函数对象的语法,但它仍然与前面的代码基本相同:编译器生成一个类构造函数和operator()
。我们将括在方括号中的值传递给构造函数,(int x) { return x < pivot; }
基本上成为该类 2 的operator()
的主体。
这使代码更容易编写和更容易阅读 - 但它并没有改变我们创建对象的基本事实,&#34;捕获& #34;构造函数中的某个状态,并使用重载的operator()
进行比较。
当然,比较恰好是排序等事情所需要的。 是更常见的lambda表达式和函数对象的常见用法,但我们当然不限于此。再举一个例子,让我们考虑&#34;正常化&#34;双打的集合。我们想要找到最大的那个,然后除以该集合中的每个值,因此每个项目的范围在0.0到1.0之间,但是所有项目都保持相同的比率,如前所述:
double largest = * std::max_element(begin, end);
std::for_each(begin, end, [largest](double d) { return d/largest; });
这里我们又有几乎相同的模式:创建一个存储一些相关状态的函数对象,然后重复应用该函数对象operator()
来完成实际工作。