本节总的思路:
1.需要定制操作的时候可以给算法传入函数
2.参数个数不匹配的时候,可以用lambda表达式
3.很多重复的操作不适合用lambda表达式可以用bind
向算法传递函数
sort函数不有一个重载版本,它的第3个参数是一个谓词
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2,size();
}
sort(v.begin(), v.end(), isShorter);
谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。
接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。
谓词分为两类:
-
一元谓词(只接受一个参数)
-
二元谓词
关于谓词:参考这篇 https://blog.youkuaiyun.com/caroline_wendy/article/details/15378055
除sort外,还有stable_sort, 称为稳定排序算法(维持相等元素的原有顺序)
stable_sort(v.begin(), v.end(), isShorter);
假如v中的元素是按字典顺序排好的,我们在调用stable_sort按长度排序时,等长元素的顺序不会变
如果想要查找序列中大于给定长度的元素,
bool check_size(const string &s, string::size_type sz)
{
return s.size() >= sz;
}
遗憾的是查找算法find_if只接受一元谓词
这就需要用到lambda表达式了
lambda表达式
它是一个可调用对象
[capture list](parameter list) -> return type { function body }
可以省略参数列表和返回类型
auto f = []{ return 42; };
cout << f() << endl;
理解时,可以把lambda表达式理解为未命名的内联函数
lambda表达式可以捕获和它属于同一个局部作用域的变量
[sz](const string &a) { return a.size() >= sz; };
这样它就成了一个一元谓词,可以被find_if调用
string::size_type sz = 10;
find_if(v.begin(), v.end(), [sz](const string &a){ return a.size() >= sz; } );
lambda可以直接使用定义在当前函数之外的名字
for_each(v.begin(), v.end(), [](const string &s){ cout << s << " "; });
cout就是定义在函数体之外的名字
lambda捕获
-
值捕获
-
引用捕获
-
隐匿捕获
-
混合捕获
值捕获
-
与参数的值传递类似
-
区别在于:拷贝过程发生在lambda创建时,而不是调用时
引用捕获
-
与参数的引用传递类似
-
同样要保证lambda调用时捕获的引用指向的局部变量还在
尽量减少捕获的数据,来避免潜在的问题。如果可能的话,避免捕获指针或引用
隐式捕获
不指明捕获哪个局部变量,而是让编译器自己去推断。
为了指示编译器需要推断捕获列表,需要在捕获列表中写一个&或=
find_if(v.begin(), v.end(), [=](const string &s){ return s.size() >= sz; });
隐式捕获sz,且是一种值捕获
混合捕获
如果混合使用显式捕获和隐式捕获
-
必须把隐式捕获放在前面
-
显式和隐式必须采用不同的捕获方式
可变lambda
通常值传递的时候,我们是无法通过形参改变实参的值的。在lambda中,可以使用mutable关键字做到
[v1]() mutable { return ++v1; };
返回类型
如果lambda体中包含除return语句之外的任何语句,则编译器假定此lambda返回void
[](int i){ if(i<0) return -i; else return i;};
该lambda包括return之外的语句(if),又没有指明返回类型,因此编译器推断它返回void,但实际函数体中返回的却是int,因此报错
此时应当指明返回类型。
[](int i) -> int { if(i<0) return -i; else return i;};
lambda使用尾置返回类型
参数绑定
如前面提到的,如果我们需要在很多地方使用相同的操作,定义一个函数比多次编写lambda表达式要好的多;类似的,如果lambda体包含多条语句,使用函数会比较清晰。
但是遇到只接受一元谓词的情况我们又不能使用函数来完成任务,因此标准库提供了bind函数。
它定义在头文件functional中。接受一个可调用对象,输出一个新的可调用对象,以便能适配参数列表。
auto newCallable = bind(callable, arg_list);
-
arg_list中callable的参数列表
-
arg_list中可以使用占位符_n
-
_1表示使用newCallable的第一个参数,以次类推
find_if(v.begin(), v.end(), bind(check_size, _1, sz));
点位符定义在命名空间std::placeholders中,通常我们一次只引用一个标识符,如using std::placeholders::_1,using std::placeholders::_2,我们也可以直接将整个命名空间都引入进来
using std::placeholders;
我们可以通过修改bind参数的顺序来调整函数的功能
sort(v.begin(), v.end(), isShorter);
sort(v.begin(), v.end(), bind(isShorter, _2, _1));
运行的结果截然相反,一个是从小到大排列,一个是从大到小排列
绑定引用参数
bind的那些非点位符参数都是通过拷贝的形式传递的,如果需要使用引用的形式传递,可以使用ref关键字
for_each(v.begin(), v.end(), bind(print, ref(os), _1, ' '));