定制操作
C++允许我们自己决定算法的操作方式,这就叫做定制操作。
1、 向算法传递函数
向算法传递函数是通过谓词完成的,
谓词:
是什么:一个可调用的表达式(如同平常的“<”, “>”, “==”)
返回结果:其返回结果是一个能 用作条件的值(这个条件可以用来判断元素的删、改、排序等操作)
类型:标准库算法所使用的谓词可分为两类:一元谓词和二元谓词。
例子:为了使排序先按单词的长度,再按字母表中的顺序进行排序,故采用接受一个二元谓词参数的sort版本用 这个谓词 代替 “<” 来比较元素。完成这个操作的函数为isShorter()。
//比较函数,用来按长度排序单词
bool isShorter(const string &s1, const string &s2)
{
return s1.size()<s2.size();
}
//按长度由短至长排序words
sort(words.begin(), words.end(), isShorter);
2、 lambda表达式
C++Primer中这一节根据解决“完整的biggies”这一方式介绍lambda表达式,其中调用find_if、for_each算法都是为了解决这一项目的补充,看这一节要抓住主线!
为什么要用到lambda表达式呢?(或者什么情况下用它呢?)[来自知乎:Lambda 表达式有何用处?]
在某处就真的只需要一个能做一件事情的函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。Lambda 表达式就可以用来做这件事。利用Lambda表达式,可以方便的定义和创建匿名函数
2.1 lambda简介
一个lambda表达式表示一个可调用的代码单元,可以将其理解为一个未命名的内联函数。
lambda函数与普通函数类似:
与普通函数相同点:具有一个返回类型、一个参数列表、一个函数体
与普通函数不同点:①lambda可能定义在函数内部 ②lambda必须使用尾置返回。
lambda函数形式:
[capture list] (params list) mutable exception-> return type { function body }
各项具体含义如下
- capture list:捕获外部变量列表
- params list:形参列表
- mutable指示符:用来说用是否可以修改捕获的变量
- exception:异常设定
- return type:返回类型
- function body:函数体
此外,可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:
序号 格式
序号 | 格式 |
---|---|
1 | [capture list] (params list) -> return type {function body} |
2 | [capture list] (params list) {function body} |
3 | [capture list] {function body} |
其中:
- 格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
- 格式2在格式1基础上省略了返回类型,编译器根据函数题内是否有return语句进行推断返回类型或viod类型。
- 格式3为最简化形式,必须永远包含捕获列表和函数体。
2.2 使用捕获列表
[capture list] 即为捕获列表, 为捕获外部变量的列表。
芮苒一个lambda可以出现在一个函数中,使用其局部变量(该局部变量也为lambda的外部变量),但它只能使用那些明确指明的变量。也就是使用 [capture list]来指明lambda的内部可以使用的变量,这一过程称过Lambda表达式“捕获”了外部变量。
捕获外部变量分为以下几种:
1、值捕获
类似参数传递中的值传递,采用值捕获的前提是变量可以拷贝。
与参数传递不同的是,被捕捉的变量的值是在lambda创建时拷贝,而不是调用时拷贝:
void fcn1()
{
size_t v1 =42; //局部变量
//将v1拷贝到名为f的可调用对象
auto f = [v1]{return v1;};
v1 = 0;
auto j = f(); //j为42;f保存了我们创建它时v1的拷贝
}
由于被捕获变量的值实在lambda创建时拷贝,因此随后对其修改不会影响到lambda内对应的值。
2、引用捕获
使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&。代码如下:
void fcn2()
{
size_t v1 =42; //局部变量
//将v1拷贝到名为f的可调用对象
auto f = [v1]{return v1;};
v1 = 0;
auto j = f(); //j为0;f2保存v2的引用,而非拷贝
}
注意:①当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。②引用捕获的变量使用的实际上就是该引用所绑定的对象。③尽量减少捕捉数据量,如果有可能的话,应该避开捕捉指针和引用。
3、隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
隐式值捕获示例:
int main()
{
int a = 123;
auto f = [=] { cout << a << endl; }; // 值捕获
f(); // 输出:123
}
隐式引用捕获示例:
int main()
{
int a = 123;
auto f = [&] { cout << a << endl; }; // 引用捕获
a = 321;
f(); // 输出:321
总结:
捕获形式 | 说明 |
---|---|
[] | 不捕获任何外部变量 |
[names] | names是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量 |
[&] | 以引用形式捕获所有外部变量 |
[=] | 以值的形式捕获所有外部变量 |
[=, &x] | 变量x以引用形式捕获,其余变量以传值形式捕获 |
[&, x] | 变量x以值的形式捕获,其余变量以引用形式捕获 |
3、参数绑定
①如果lambda 的捕获列表为空,通常可以用函数来代替它。
②若捕获列表不为空,不容易用函数替换,
某些函数只接受一元谓词,导致重复编写lambda,为解决这个问题引进的bind函数。
对于那种只在一两个地方使用的简单操作, lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数, 而不是多次编写相同的lambda表达式。类似的,如果一个操作需要很多语句才能完成,通常使用函数更好,于是会出现了bind函数。
3.1 bind函数简介
头文件:functional
性质:一个函数模板,类似一个函数适配器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
函数原型:std::bind返回一个基于 f 的函数对象,其参数被绑定到args上。
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
bind的参数:假定f是一个可调用对象,它有5个参数,则bind的调用为:
//g是一个有两个参数的可调用对象
auto g = bind(f, a, b, _2, c, _1);//将 _2(第三个参数)和_1(第五个参数)传递给 f
//f的第一个、第二个和第四个参数分别绑定到给定的值a、b和c上
对于g参数来说,其形式为g(_1, _2),映射为f(a, b, _2, c, _1),即g(X, Y) = f(a, b, Y, C, X);
原理:通过bind函数,可以将多个可调用对象绑定到一个或几个参数上。上例中就是将多个可调用参数绑定到Args上。
auto newCallable = bind(callable, arg_list); //arg_list是一个逗号分隔符的参数列表
arg_list多个参数绑定到callable上,当其他函数调用newCallable时,newCallable会调用callable。
参考文献
[1] C++Primer第五版
[2] C++ 11 Lambda表达式(作者:dsw846)
[3] C++11新特性:参数绑定——std::bind (作者:___Blue_H)