C++ 新特性 lambda表达式与匿名函数
lambda 表达式
Lambda 表达式 是一个源自阿隆佐•丘奇(Alonzo Church) -- 艾伦•图灵(Alan Turing)的老师--的术语。
丘奇创立了 \(\lambda\) 演算,后来被证明和图灵机是等价的。
Lambda 表达式是 C++11 中最重要的新特性之一,而 Lambda 表达式就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。这样的场景很多,所以匿名函数几乎是现代编程语言的标配。
1. Lambda 表达式基础
Lambda 表达式的基本语法如下:
[捕获列表]() mutable(可选) 异常属性 -> 返回类型 {
// 函数体
};
[ caputrue ] ( params ) opt -> ret { body; };
- Lambda 表达式以一对中括号开始
- 跟函数定义一样,我们有参数列表
- 跟正常的函数定义一样,我们会有一个函数体,里面会有
return
语句 - Lambda 表达式一般不需要说明返回值(相当于 auto);有特殊情况需要说明时,则应使用箭头语法的方式
- 每个 Lambda表达式都有一个全局唯一的类型,要精确捕捉 Lambda 表达式到一个变量中,只能通过
auto
声明的方式
基本使用
- 参数列表
- 返回类型
- 函数体
#include <iostream>
using namespace std;
int Foo(int a, int b) {
return a + b;
}
int main() {
// lambda 表达式就是匿名函数(没有名字的函数)
// []捕获列表 ()参数列表 -> 返回值
int c = [](int a, int b) -> int {
// 函数体
return a + b;
}(1, 2);
cout << c << endl;
auto f = [](int a, int b) -> int {
// 函数体
return a + b;
};
c = f(1, 2);
cout << c << endl;
// 函数式编程 多线程,并发
int d = [](int n) {
return [n](int x) {
return n + x;
}(1);
}(2);
cout << d << endl;
auto fun = [](int n) {
return [n](int x) {
return n + x;
};
};
c = fun(1)(2);
cout << c << endl;
}
2. mutable
#include <iostream>
using namespace std;
int main() {
int t = 10;
auto f = [t]() {
return ++t; // 报错,"t": 无法在非可变 lambda 中修改通过复制捕获
};
}
#include <iostream>
using namespace std;
int main() {
int t = 10;
// 按值捕获
auto f = [t]() mutable{
return ++t; // t 拷贝了一份,独一无二
};
cout << f() << endl; // 11
cout << f() << endl; // 12
cout << t << endl; // 10
}
#include <iostream>
using namespace std;
int main() {
int t = 10;
// 按值捕获
auto f = [t]() mutable{
return ++t; // t 拷贝了一份,独一无二
};
auto f2 = [t]() mutable{
return ++t; // t 拷贝了一份,独一无二
};
cout << f() << endl; // 11
cout << f2() << endl; // 11
cout << f() << endl; // 12
cout << f2() << endl; // 12
cout << t << endl; // 10
}
3. 捕获列表
所谓捕获列表,其实可以理解为参数的一种类型,lambda 表达式内部函数体在默认情况下是不能够使用函数体外部变量的,这时候捕获列表可以起到传递外部参数的作用。根据传递的行为,捕获列表也分为以下几种:
1. 值捕获
与参数传值类似,值捕获的前提是变量可拷贝,不同之处在于,被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝:
void learn_lambda_func_1() {
int value_1 = 1;
auto copy_value_1 = [value_1]() {
return value_1;
};
value_1 = 100;
auto stored_value_1 = copy_value_1();
// 这时,stored_value_1 == 1, 而 value_1 == 100
// 因为 copy_value_1在创建时就保存了一份 value_1 的拷贝
cout << "value_1 = " << value_1 << endl;
cout << "stored_value_1 = " << stored_value_1 << endl;
}
2. 引用捕获
与引用传参类似,引用捕获保存的是引用,值会发生变化
#include <iostream>
using namespace std;
int main() {
int t = 10;
// 按值捕获, 捕获的是声明匿名函数时,捕获列表的参数的值
// auto f = [t]() {
// cout << t << endl;
// };
// f();
// 按引用捕获
auto f2 = [&t]() {
cout << t << endl;
t = 13;
};
t = 11;
f2(); // 11
cout << t << endl; // 13
// 捕获列表
int a = 1;
int b = 2;
int c = 3;
int d = 4;
[a, b]() {
cout << a << " + " << b << endl;
}();
// 捕获所有的变量 按值捕获
[=]() {
cout << a << " + " << b << endl;
}();
// 按引用捕获所有引用
[&]() {
cout << a++ << " + " << b++ << endl;
}();
// 按照值和引用的方式捕获变量
[a,b,&c,&d]() {
cout << a << "+" << b << endl;
cout << c++ << " + " << d++ << endl;
}();
}
3. 隐式捕获
手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个 &
或 =
向编译器声明采用 引用捕获或者值捕获
总结一下,捕获提供了 lambda 表达式对外部值进行使用的功能,捕获列表的最常用的四种形式是
[]
空捕获列表[name1, name2, ...]
捕获一系列变量[&]
引用捕获,让编译器自行推导捕获列表[=]
值捕获,让编译器推导应用列表
4. 使用案例
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = { 1, 2, 3, 4, 5 };
for (int i = 0; i < v.size(); ++i) {
if (v[i] % 2 == 0) {
cout << v[i] << "是偶数" << endl;
} else {
cout << v[i] << "是奇数" << endl;
}
}
// 高阶写法
for_each(v.begin(), v.end(), [](int n) {
if (n % 2 == 0) {
cout << n << "是偶数" << endl;
} else {
cout << n << "是奇数" << endl;
}
});
}