C++基础知识6:lambda表达式
本文目的
简单的介绍一下lambda表达式在不同地方使用的作用,在STL算法函数,在类里面的使用,以及相关应用举例
前置与关联知识点
- C++基础知识1:仿函数function objects
- 二元谓词/二元判断式,可以参考数据结构与函数使用2:排序函数sort
lambda表达式简介
定义lambda的时候,编译器生成一个与lambda对应的新的未命名的类类型
参考Primer C++(第五版) 10.3.2 lambda 表达式 p346
到目前为止,我们使用过的仅有的两种可调用对象是函数和函数指针。
还有其他两种可调用对象:重载了函数调用运算符的类,以及lambda表达式(lambda expression)。
lambda表达式的形式
[capture list](parameter list)->return type {function body}
//[捕获列表](参数列表)->返回值 {函数主题}
//一个典型的例子
- 一般返回值根据函数体的代码推断返回类型,所以可以忽略
auto cmp = [](const int & lhs, const int & rhs){return lhs<rhs;};
- 没有参数的时候也可以忽略参数列表,如下
int cap1; auto cmp = [cap1]{return cap1;};
lambda表达式使用的意义
- 在C++中引入 lambda是一个类似于函数的表达式,用于代替函数指针或函数符。
好处
- 距离:和函数指针相比,可以函数内进行定义,让定义位于使用的地方附近,便于查看和修改。
- 简洁:和函数符相比,实现的代码更加简单。
- 效率:取决于编译器内联哪些东西(不是很懂)。
- 功能:通过捕获的方式访问作用域内的任何动态变量。
lambda代替函数符或者函数指针像算法传递可调用对象(谓词)
1. 向STL算法传递二元谓词
举个例子,以sort函数为例子
//对nums进行降序排序
vector<int> nums = {1,2,3,1,3,5,2,1}
//方式1
auto cmp = [](const int & lhs, const int & rhs){return lhs>rhs;};
sort(nums.begin(),nums.end(),cmp);
//方式2
sort(nums.begin(),nums.end(),[](const int & lhs, const int & rhs){return lhs<rhs;});
2. 作为模板参数
举个例子,以priority_queue为例子
//创建一个大根堆
vector<int> nums = {1,2,3,1,3,5,2,1}
//1. 定义lambda表达式
auto cmp = [](const int & lhs, const int & rhs){return lhs>rhs;};
//2. 定义priority_queue,
priority_queue<int,vector<int>, decltype(cmp)> pqueue;
sort(nums.begin(),nums.end(),[](const int & lhs, const int & rhs){return lhs<rhs;});
//3. 填充元素
for(auto num:nums)
pqueue.push(num);
lambda表达式使用捕获列表
参考Primer C++(第五版) 10.3.2lambda表达式 p345
虽然一个 lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指明的变量。一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。捕获列表指引 lambda在其内部包含访问局部变量所需的信息。
参考Primer C++(第五版) 10.3.2lambda表达式 p348
一个lambda只能使用他捕获列表中存在的他所在函数中的局部变量。
这里举一个最简单的例子,用于比较一个输入的形参是否比judge值大。
void myfun()
{
int judge = 100;
auto judge_fun = [judge](int aim){return judge < aim;};
// auto judge_fun = [](int aim){return judge < aim;};这一行不捕获直接使用judge就会报错
cout<<judge_fun();
}
lambda表达式捕获
参考Primer C++(第五版) 10.3.3lambda捕获和返回 p352
类似于参数传递,变量的捕获的方式可以是值或者引用
1. 值捕获
值捕获的前提是变量可以拷贝,而且是在lambda创建的时候拷贝,而非调用时
void fcn1()
{
int v1 = 42;
auto f = [v1]{ return v1 ;};
v1 = 0;
auto j = f(); //j = 42而不是0,lambda创建后修改v1不会改变捕获的值
}
2. 引用捕获
需要保证运行的时候引用捕获的值依然存在
void fcn1()
{
int v1 = 42;
auto f = [&v1]{ return v1 ;};
v1 = 0;
auto j = f(); //j = 42而不是0,lambda创建后修改v1不会改变捕获的值
}
- 存在一些无法拷贝的对象的时候回去使用引用(如ostream对象只能接收普通引用)。
3. 隐式捕获
非显式,让编译器推断捕获列表,可以看上面的表格的写法。
4. 可变lambda
- 默认情况下lambda对于被拷贝的变量也不会改变这个值,如果需要改变需要+mutable
void fcn1() { int v1 = 42; auto f = [v1]{ return ++v1 ;}; v1 = 0; auto j = f(); //j = 43 }
引用捕获的话同一般的函数。
指定lambda返回类型
有时候编译器无法推断返回类型,比如有多个等价的if语句里面的return。
auto fcn = [](int i){if (i < 0) return -i; else return i;};//会报错
auto fcn = [](int i)->int{if (i < 0) return -i; else return i;};//不会报错
参考文献
Primer C++(第五版) 10.3.2lambda表达式 p345
Primer C++(第五版) 10.3.2 lambda 表达式 p346
Primer C++(第五版) 10.3.2lambda表达式 p348
Primer C++(第五版) 10.3.3lambda捕获和返回 p352