C++11标准中新增了lambda函数
关于lambda函数,百度百科中的解释是这样的:
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。
在C++11前,为实现一个让奇数排在前面,偶数排在后面,并且从小到大排列的程序可能要这样写:
// 编译环境:MinGW-W64 8.1.0
#include <iostream>
#include <algorithm>
using namespace std;
bool fun(const int &a, const int &b) {
if ((a % 2 == 1) && (b % 2 == 0))
return true;
if (a < b) return true;
return false;
}
int main() {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
sort(a, a + 10, fun);
for (int i = 0; i < 10; i++)
cout << a[i] << ' ';
// 输出:1 3 5 7 9 2 4 6 8 10
return 0;
}
可以看到,我们定义了一个函数fun并实现了这个排序,但真的有必要再写一个完整的全局函数吗?
C++11中的lambda就很好地解决了这一问题,我们先来看一段代码:
#include <iostream>
using namespace std;
int main() {
auto f = [](int x){ return x * x; };
cout << f(20) << endl;
return 0;
}
我们用auto声明了一个变量f,并将一个长成这样的东西赋给了它:[](int x){ return x * x; }
这个就是lambda。
lambda的格式:
[捕获列表]<模板声明>(参数列表)mutable 异常说明->类型{函数体}
其中:
格式 | 内容 |
---|---|
捕获列表 | 允许访问当前作用域下的某一个(些)变量 |
模板声明 | 与普通函数的模板相同(但省略template关键字) |
参数列表 (可选) | lambda函数的参数,若无参数,可以连括号一起省略(当然也可以保留,不过没必要),不能有不定参数,不能有默认参数,参数必须有名称(C++14标准允许以auto作为参数类型) |
mutable (可选) | 指定捕获列表以值传递的变量可以被修改,但不会改变变量本身(相当于普通函数的值传递),引用传递的变量不变 (捕获列表的变量传递方式见下表) |
异常说明 (可选) | 指定lambda可能抛出的异常,例如throw(int) 、noexcept |
类型 (可选) | 指定返回值类型,包括void类型(如果返回类型比较明显,可以省略,让编译器自动推断,省略时要连同箭头-> 一起省略)如果省略了参数列表的括号,那么不能指定此项 |
函数体 | 函数中要执行的代码,如果没有return语句且没指定类型,默认返回类型为void |
捕获列表语法:
表达式 | 作用 |
---|---|
[] | 什么也不捕获 |
[var] | 捕获当前作用域var变量,值传递(不能修改捕获变量值) |
[&var] | 捕获当前作用域var变量,引用传递(可以修改捕获变量值) |
[=] | 捕获当前作用域所有变量,值传递 |
[&] | 捕获当前作用域所有变量,引用传递 |
[=,&var] | 捕获当前作用域所有变量,以值传递,但以引用传递var(也可以使用[&,var]来进行值传递) |
[this] | 捕获class的this指针,如果捕获列表指定了[=]或[&],则会自动传递this (PS:不能捕获成员变量,只能捕获this指针,用this指针访问成员变量) |
另外,捕获列表可以传递多个变量,以逗号分隔
// 最短的lambda函数
[]{}
一个模板例子
[]<class T>(T v) noexcept -> void{
cout << v << endl;
}("blog.csdn.net");
所以,我们就可以用lambda来实现刚刚的sort里的比较函数:
// 编译环境:MinGW-W64 8.1.0
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
sort(a, a + 10, [](const int &a, const int &b){
if ((a % 2 == 1) && (b % 2 == 0))
return true;
if (a < b) return true;
return false;
});
for (int i = 0; i < 10; i++)
cout << a[i] << ' ';
// 输出:1 3 5 7 9 2 4 6 8 10
return 0;
}
接下来是踩坑环节!
#include <iostream>
using namespace std;
int foo(int (*f)(int), int x) {
return f(x);
}
int main() {
cout << foo([](int a)->int{ return a * 5 + 7; }, 2) << endl;
// 输出 17,一切正常
int Integer = 123;
cout << foo([Integer](int a)->int{ return a * 5 + Integer; }, 2) << endl;
// 编译错误
return 0;
}
错误信息:
可以看到,我们的[](int a)->int{ return a * 5 + 7; }
可以隐式转换为int (*)(int)
类型来传递,但添加了捕获参数的lambda函数不能转换为函数指针。
解决方法:
- 使用模板参数(因为lambda的本质是一个临时对象)。
- 使用
functional
头文件封装的std::function
类型。
正确改法:
- 模板参数
#include <iostream>
using namespace std;
template<class Fn>
int foo(Fn f, int x) {
return f(x);
}
int main() {
cout << foo([](int a)->int{ return a * 5 + 7; }, 2) << endl;
// 输出 17
int Integer = 123;
cout << foo([Integer](int a)->int{ return a * 5 + Integer; }, 2) << endl;
// 输出 133
return 0;
}
- std::function
#include <iostream>
#include <functional>
using namespace std;
int foo(function<int(int)> f, int x) {
return f(x);
}
int main() {
cout << foo([](int a)->int{ return a * 5 + 7; }, 2) << endl;
// 输出 17
int Integer = 123;
cout << foo([Integer](int a)->int{ return a * 5 + Integer; }, 2) << endl;
// 输出 133
return 0;
}