## lambda基础使用
`lambda` 表达式(lambda expression)是一个匿名函数,`lambda` 表达式基于数学中的 `λ` 演算得名。C++11中的lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。
lambda表达式的基本构成:

```cpp
[capture list] (params list) mutable exception-> return type { function body }
```
#### ① 函数对象参数
`[]` 标识一个 `lambda` 的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义 `lambda` 为止时 `lambda` 所在作用范围内可见的局部变量(包括lambda所在类的this)。函数对象参数有以下形式:
- `空`:没有使用任何函数对象参数
- `=`:函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)
- `&`:函数体内可以使用lambda所在作用范围内所有可见的局部变量(包括lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)
- `this`:函数体内可以使用lambda所在类中的成员变量
```
[&a]:将a按引用进行传递
[a, &b]:将a按值进行传递,b按引用进行传递
[=,&a, &b]:除a和b按引用进行传递外,其他参数都按值进行传递
[&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递]
注意:捕捉列表是不允许变量重复传递,例如
[=, a]:这里 = 已经以值传递的方式捕捉了所有变量,捕捉 a 重复
[&, &this]:这里 & 已经以引用传递的方式捕捉了所有变量,再次捕捉 this 也是一种重复
```
#### ② 参数列表
标识重载的()操作符的参数,与普通函数的参数列表相同,没有参数时,可以连同括号一起省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
#### ③ 可修改标示符
`mutable` 声明,这部分可以省略。默认情况下,`lambda` 函数总是一个 `const` 函数,按值传递函数对象参数时,加上 `mutable` 修饰符后,可以取消其常量性,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
#### ④ 错误抛出标示符
`exception` 声明,这部分也可以省略。`exception` 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 `throw(int)`
#### ⑤ 函数返回值
`->return-type`:返回值类型。标识函数返回值的类型,当返回值为 `void`,或者函数体中只有一处 `return` 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
#### ⑥ 函数体
内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量
```cpp
class Test {
public:
void func(int x, int y) {
auto x0 = [] {};
//auto x1 = [] {return i; }; //err, 没有捕获外部变量
auto x2 = [=] {return i + x + y; }; //ok, 值传递方式捕获所有外部变量
auto x3 = [&] {return i + x + y; };//ok, 引用传递方式捕获所有外部变量
auto x4 = [this] {return i; }; //ok, 捕获this指针
//auto x5 = [this] {return i + x + y};//err, 没有捕获x, y
auto x6 = [this, x, y] {return i + x + y; };//ok, 捕获this指针, x, y
auto x7 = [this] {return i++; };//ok, 捕获this指针, 并修改成员的值
}
public:
int i = 0;
};
void test1() {
int a = 0, b = 1;
//auto f1 = [] {return a; };//err, 没有捕获外部变量
auto f2 = [a] {return a; }; //ok, 值传递方式捕获 a 变量
auto f3 = [=] {return a + b; }; //ok, 值传递方式捕获所有外部变量
//auto f4 = [=] {return a++; };//err, a是以赋值方式捕获的,无法修改
auto f5 = [a]() mutable {return a++; };/ok, 加上mutable修饰符后,可以修改按值传递进来的拷贝
auto f6 = [&] {return a++; };//ok, 引用传递方式捕获所有外部变量, 并对a执行自加运算
//auto f7 = [a] {return a + b; };//err, 没有捕获变量b
auto f8 = [a, &b] {return a + (b++); }; //ok, 捕获a, &b
auto f9 = [=, &b] {return a + (b++); };//ok, 捕获所有外部变量,&b
}
```
#### 值传递与引用传递的区别
```cpp
int main() {
int j = 12;
auto by_val_lambda = [=] { return j; };
auto by_ref_lambda = [&] { return j; };
cout << "by_val_lambda: " << by_val_lambda() << endl;
cout << "by_ref_lambda: " << by_ref_lambda() << endl;
cout << "j = " << j << endl;
j++;
cout << "by_val_lambda: " << by_val_lambda() << endl;
cout << "by_ref_lambda: " << by_ref_lambda() << endl;
cout << "j = " << j << endl;
return 0;
}
```
运行结果如下所示:

在这个例子中当 `by_val_lambda` 捕获到 `a` 的值时就已经被赋值在其中不会被更改了,无论后面如何修改 `a` 的值, `by_val_lambda`的返回结果都不会改变。但是 `by_ref_lambda` 的返回结果会随着捕获变量的值改变而改变。
## lambda与仿函数
在 `STL`中会用到一种特殊的对象,通常称为函数对象,或者仿函数。就是重载了 `()` 运算符的自定义类型对象。仿函数的具体使用这里就不做过多赘述。
```cpp
class MyFunctor {
public:
int operator()(int x, int y) { return x + y; }
};
int main() {
int a = 1, b = 2;
MyFunctor add;
cout << add(a, b) << endl;
return 0;
}
```
上述代码在使用的时候其实与仿函数非常相似,当他们放在一起时如下所示:
```cpp
class MyFunctor {
public:
MyFunctor(int x) :round(x) {}
int operator()(int tmp) { return tmp + round; }
private:
int round;
};
void test2() {
//仿函数
int round = 2;
MyFunctor add1(round);//调用构造函数
cout << "result1 = " << add1(1) << endl; //operator()(int tmp)
//lambda表达式
auto add2 = [round](int tmp)->int {return tmp + round; };
cout << "result2 = " << add2(1) << endl;
}
```
在该例中,分别使用了仿函数与 `lambda` 两种方式实现的加法。`lambda` 函数捕捉了 `round` 变量,而仿函数则通过 `round` 初始化类。其它的,如在传参上二者保持一致,出去再语法层面上的不同,而这有着相同的内涵--都可以捕捉一些变量作为初始状态,并接受参数进行运算。
而事实上,仿函数是编译器实现lambda的一种方式,通过编译器都是把lambda表达式转化为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式。
## Lambda与static inline函数
`lambda` 函数可以省略外部声明的 `static inline` 函数,其相当于一个局部函数。局部函数仅属于父作用域,比起外部的 `static inline` 函数,或者是自定义的宏,`lambda` 函数并没有实际运行时的性能优势(但也不会差),但是 `lambda` 函数可读性更好。
父函数结束后,该 `lambda` 函数就不再可用了,不会污染任何名字空间。
## lambda类型
`lambda` 表达式的类型在 C++ 11 中被称为“闭包类型”,每一个 `lambda` 表达式则会产生一个临时对象(右值)。因此,严格地将,`lambda` 函数并非函数指针。
不过 C++ 11 标准却允许 `lambda` 表达式向函数指针的转换,但提前是 `lambda` 函数没有捕获任何变量,且函数指针所示的函数原型,必须跟 `lambda` 函数函数有着相同的调用方式。
```cpp
void test3() {
int a = 3, b = 4;
auto totalChild = [] (int x, int y)->int{return x + y; };
typedef int (*allChild)(int, int);
typedef int (*oneChild)(int);
allChild p = totalChild;
//oneChild q = totalChild; 错误,参数类型必须一致
decltype(totalChild) p2 = totalChild; //需要通过 decltype 获得lambda 的类型
//decltype(totalChild) p3 = p; //错误,指针无法转化为 lambda
}
```
所以,没有捕获变量的lambda表达式可以直接转换为函数指针,而捕获变量的lambda表达式则不能转换为函数指针。
## lambda 与 STL
将 `lambda` 应用到 `STL` 的相关算法中,可以极大程度的简化别写的难度,提升代码的阅读质量。以使用 `for_each` 为例。
```cpp
vector<int> nums;
vector<int> largeNums;
class LNums {
public:
LNums(int u) : ubound(u) {} //构造函数
void operator () (int i) const {//仿函数
if (i > ubound) {
largeNums.push_back(i);
}
}
private:
int ubound;
};
void test4() {
//初始化数据
for (int i = 0; i < 10; i++)
nums.push_back(i);
int ubound = 5;
//1、传统的for循环
for (auto i = nums.begin(); i != nums.end(); i++)
if (*i > ubound)
largeNums.push_back(*i);
//2、使用仿函数
for_each(nums.begin(), nums.end(), LNums(ubound));
//3、使用lambda函数和算法for_each
for_each(nums.begin(), nums.end(), [=](int i) {
if (i > ubound)
largeNums.push_back(i);
});
//4、遍历元素
for_each(largeNums.begin(), largeNums.end(), [=](int i) {
cout << i << " ";
});
cout << endl;
}
```
C++11:lambda表达式