参考资料: C++primer(第五版)
基本概念
可调用对象:函数、函数指针、lambda表达式、bind创建的对象、重载了函数调用运算符的类(函数对象),其中
- lambda是函数对象。编译器将lambda表达式翻译成一个未命名类的未命名对象,该类中含有一个重载的函数调用运算符
- bind,通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
- 重载了函数调用运算符的类:若类定义了调用运算符,则该类的对象为函数对象。(因为可以调用这种对象,所以说这些对象的行为像函数一样)
谓词:可调用的表达式,返回结果是一个能用做条件的值,有 一元谓词(接受单一参数)、二元谓词(两个参数)
联系和区别
- 所有谓词都是可调用对象,但不是所有的可调用对象都是谓词
- 谓词是一种特殊的可调用对象,用来表示一个条件,谓词的调用结果必须为 bool 类型,表示一个条件是否满足
- 可调用对象的返回类型可以是任何类型
示例
以标准库的常用算法为例
C++primer附录中给出的算法,其作为可调用对象or谓词的参数类型如下:
对于不同的算法,其参数要求不同,详见附录。
示例1:unaryOp
我这里编写了一个show模板类函数,其实现调用for_each标准库算法。
for_each算法的第三个参数为unaryOp,也就是使用输入序列的一个实参的可调用对象。
我编写了两个匿名函数:
- 第一个lambda为一个参数,可以直接传入for_each算法
- 第二个lambda为两个参数,直接传入会报错,这里采用bind适配,输出小于5的值(bind中,_1, _2 在std::placeholders命名空间里)
template <typename T>
void show(const T &t)
{
// =================================================================
// for_each(,,unaryOp) 使用输入序列的一个实参来调用
auto lamb_func = [](const int &i)
{ cout << i << " "; };
// =================================================================
// bind
// 两个参数的可调用对象(返回较小值), 直接传入for_each 会报错
auto lamb_func_2 = [](const int &x, const int &y)
{ cout << (x < y ? x : y) << " "; };
// 通过bind适配 (返回小于5的值)
auto func = bind(lamb_func_2, std::placeholders::_1, 5); // _1, _2 在std::placeholders命名空间里
// =================================================================
// vector<int> vec = {4, 5, 3, 6, 1, 8, 2, 9, 0, 7};
// test 1
for_each(t.begin(), t.end(), lamb_func); // result: 4 5 3 6 1 8 2 9 0 7
// test 2
for_each(t.begin(), t.end(), func); // result: 4 5 3 5 1 5 2 5 0 5
// =================================================================
cout << endl;
}
示例2:comp
以sort标准库函数为例,其第三个参数为 comp 二元谓词,需要满足关联容器中对关键字序的要求
关联容器中对关键字序的要求:默认情况下,标准库使用关键字类型的 < 运算符来比较两个关键字,可以向算法提供我们自己定义的操作来替代关键字上的 < 运算符,所提供的操作必须在关键字类型上定义一个严格弱序,可以将严格弱序看作“小于等于”。(详见C++primer)
这里我编写了 函数、重载了函数调用运算符的类、函数指针、Lambda表达式作为 comp谓词
最后,采用了标准库定义的函数对象,同样需要创建临时对象
// =================================================================
// 函数
bool func(int &x, int &y)
{
return x < y;
}
// =================================================================
// 重载了函数调用运算符的类
struct Comp
{
bool operator()(int &x, int &y)
{
return x < y;
}
};
// =================================================================
int main()
{
// =================================================================
// 函数指针
bool (*func_p)(int &, int &) = func;
// =================================================================
// lambda
auto lambda_fun = [](int &x, int &y)
{ return x < y; };
// =================================================================
// sort(,, comp) comp 二元谓词,满足关联容器中对关键字序的要求
vector<int> vec = {4, 5, 3, 6, 1, 8, 2, 9, 0, 7};
// Comp() 创建临时对象
sort(vec.begin(), vec.end(), Comp()); // result: 0 1 2 3 4 5 6 7 8 9
sort(vec.begin(), vec.end(), func_p); // result: 0 1 2 3 4 5 6 7 8 9
sort(vec.begin(), vec.end(), lambda_fun); // result: 0 1 2 3 4 5 6 7 8 9
sort(vec.begin(), vec.end(), func); // result: 0 1 2 3 4 5 6 7 8 9
// 标准库定义的函数对象,同样需要创建临时对象
sort(vec.begin(), vec.end(), less<int>());
// =================================================================
return 0;
}
其他
上述代码最后一行调用标准库定义的函数对象,与priority_queue的第三个参数区分,其第三个参数 需要给出每一个模板参数的类型,来实例化模板
int main()
{
// 第三个参数直接传入类型, 而不是临时对象
priority_queue<int, vector<int>, less<int>> myqueue;
// =================================================================
// 若采用lambda表达式,需要使用decltype
auto cmp = [](const int &x, const int &y)
{ return x < y; };
// 通过decltype声明 lambda表达式类型,再lambda表达式作为优先级队列参数进行初始化
priority_queue<int, vector<int>, decltype(cmp)> myqueue_2(cmp);
return 0;
}
示例3:unaryPred
以 find_if 为例,其第三个参数为 unaryPred 一元谓词
这里我使用bind适配两个参数的lambda表达式
int main()
{
vector<int> vec = {4, 5, 3, 6, 1, 8, 2, 9, 0, 7};
auto lamb_func = [](int x, int y)
{ return x == y; };
// bind将第二个参数y绑定为 3,find_if返回第一个为3的迭代器
auto func = bind(lamb_func, std::placeholders::_1, 3);
// find_if(,, unaryPred) unaryPred 一元谓词
auto res_iter = find_if(vec.begin(), vec.end(), func); // result :指向3的迭代器
return 0;
}