首先我们引入一个问题:
如果要你编一个程序,使用find_if算法来实现对一个字符串中的各个字符进行筛选并输出符合特定长度的字符,你会怎么办?
有人会说这好办,像下面这样不就可以了吗?
//省略部分内容,下同
find_if(str.begin(),str.end(),islength);
//...
bool islength(char c)
{
return c>50; //假设特定长度为50
}
但是一个灵活的程序需要有处理多变情况的能力,如果需要的特定长度不是一个常量,而是需要从在程序运行时才能获得值的变量,又应该怎么办?
当然我们首先会注意到find_if算法原则上只接受一元谓词,但从问题描述来说却需要传入两个参数,一个是字符,另一个是存储特定长度的变量,我的第一个处理方法是将该变量声明为全局变量:
//相关头文件
int len;
//...
bool islength(char c)
{
return c>len; //假设特定长度为len
}
//...
find_if(str.begin(),str.end(),islength);
但是使用全局变量也有缺点,一来是变量具有静态存储时期,但变量却只在find_if中才发挥作用,一定程度浪费了内存空间,二来是要多次使用find_if时,需要添加的全局变量声明语句过多,使代码冗杂。
因此,我们引入这篇文章的正题—使用lambda表达式。
lambda表达式,作为可调用对象之一,是一个可调用的代码单元,可视作内联函数,可定义在函数内部。语法如下
[capture list](parameter list)->return type{function body}
lambda必须永远包含捕获列表和函数体,使用尾置返回指定返回类型。
(尾置返回示例:auto func(int i)->int(*)[10];auto指示返回类型位于参数列表之后)
- 捕获列表
1.捕获列表只能是它所在函数的局部变量,不能填写全局变量(显示无法捕获具有静态存储时期的变量);
2.全局变量可直接使用,不必在捕获列表出现(如cout定义在头文件iostream,只需包括该头文件);
3.捕获列表变量可多个;
4.使用引用捕获方式/指针/迭代器:确保lambda执行时,绑定到迭代器、指针或引用的对象仍然存在,且对象具有预期的值(不被随意改变)
5.隐式捕获:
[&]
:捕获引用
[=]
:值捕获
6.混合使用
[&,c](const string & s){os<<s<<c;}
编译器默认引用捕获(无显式指明)
7.可变lambda
值捕获一般不改变其值,如果想更改所捕获变量的值,可在参数列表后加上关键字mutable。
//值捕获
void fcn1()
{
size_t v1=42;
auto f=[v1]()mutable{return ++v1;};
v1=0;
auto j=f();//j=43
}
如果使用引用捕获但是不想更改其值可以在该值声明时加上const。
//引用捕获
void fcn2()
{
size_t v1=42;
auto f=[&v1](){return ++v1;};
v1=0;
auto j=f();//j=1
}
- 参数列表
传递参数过程中需保证实参与形参数目、类型相同 - 返回类型
1.返回类型为空时:当函数体内只有一个返回表达式时编译器通过类型推断得出;若有多个返回语句,则默认返回类型为void)
2.指定返回类型
示例:
transform函数接受三个迭代器和一个可调用对象
transform(vi.begin(),vi.end(),vi.begin(),[](int i){return i<0?-i:i;});
上面程序编译成功通过,但是如果程序改为下面这样,就会出现编译错误。
transform(vi.begin(),vi.end(),vi.begin(),[](int i){if(i<0)return -i;else return i;});
这是因为存在两个return语句,且没有指定返回类型,因此编译器认定返回类型为void,然而实际却返回int值。
指定返回类型后可以成功通过。
transform(vi.begin(),vi.end(),vi.begin(),[](int i)->(int){if(i<0)return -i;else return i;});
好了,让我们回到一开始的问题,如何解决一元谓词接受两个参数(字符和长度)的问题。
//...
find_if(str.begin(), str.end(), [len](const char c){return c > len; });
//...
是不是省去了很多不必要的步骤呢?问题成功解决。