一、前言
lambda表达式是一个很有趣的东西,其出现的目的,本质上也是来简化我们程序员代码的。
这句话的意义在于,即使没有、你不学lambda表达式,你也能正常开发程序,只是有了lambda表达式之后。你代码写起来会更加舒服。
lambda表达式有时候也被称为lambda函数,或者直接简称为lambda,它的作用是快速定义、使用匿名函数对象。
匿名函数对象同样也是一个有趣的知识点,有趣的并不是匿名,而是函数对象。
举个例子:
#include<iostream>
using namespace std;
class FunObject {
public:
	int operator()(int x,int y) {
		return x + y;
	}
};
这里声明了一个FunObject的类,然后我在其中重载了()运算符。
而此时有意思的地方就来了,当我用这个类去定义一个对象后,就可以通过()的方式来调用这个函数:
int main() {
	FunObject add;
	cout<<add(10,20);
}
这就和调用普通函数一样!
但和普通函数不同的地方在于,它不仅能当函数使用,也能当类使用,也就是说,它本身还可以存储一些属性的!
所以甚至进一步说,函数对象就是对普通函数的一次升级,可以完成更强大的功能。
甚至有些时候,函数对象的调用速度会比普通函数更快,这与编译器内联优化有关。
但它虽然好用,但你也看到了,它使用起来其实并不方便,因为还需要先去声明一个类才行。
而这就是lambda的作用了,它就是用来简化这一过程的,即:生成匿名的函数对象,这样就不用去事先声明了。
二、基本使用
它的使用方法并不难,只要看懂了,我相信你一定会喜欢上使用它的。
#include<iostream>
using namespace std;
int main() {
	int a = 100;
	int b = 200;
   //使用匿名函数对象,即lambda得到一个实例化的函数对象fun,auto为自动推断类型
	auto fun = [a, b]() {
		cout << a + b;
		};
	fun();
   //可以简写为:
   	[a, b]() {
		cout << a + b;
	}(); //最后的(),代表直接调用
}
上面的代码中,[](){};结构就是一个lambda表达式的基本写法。
- []:用来捕获变量交给后面的函数体使用
- ():函数的参数
- {}:函数体,存放代码。
如果将上面的代码转换为函数对象,那就是等价于下面这样:
#include<iostream>
using namespace std;
class TestFun {
	int a;
	int b;
public:
	TestFun(int a,int b) 
		:a(a),b(b)
	{}
	void operator()() {
		cout << a + b;
	}
};
int main() {
	int a = 100;
	int b = 200;
	TestFun fun(a, b);
	fun();
}
也就是说,这个[]捕获组,实际上就是自动将其转换为函数对象的成员变量,通过构造函数的初始化列表进行赋值的。
而后面的(){},本质上就是重载运算符()的函数而已。
但通过写成lambda表达式的形式,你会发现,它真的能让我们少写超级多的代码!
三、捕获组
虽然上面说了,这个捕获组本质上就可以看作是函数对象的构造函数,但还是有一些需要注意的点。
比如像前面那样,直接填入函数体需要的变量:
[a, b]() {};
这种是通过复制实现的,对于一些结构体、字符串来说,我们可能更希望使用引用的方式:
[&a, &b]() { };
这可以避免大量拷贝。
如果觉得一个一个的填太过麻烦,也可以直接像下面这样写:
[=]() {};
直接使用一个=符号,它默认会按复制的方式捕获周围所有可用的变量。
如果你想要让某个变量按引用,其它都复制,那就可以这样写:
[=,&a](){}
这里的意思就是,除了a变量使用引用的方式,其它都使用复制的方式传入。
同样也可以反过来:
[&](){}; //按引用的方式捕获所有变量
[&,a](){}; //除了a用复制,其它都是引用
如果lambda表达式写在一个类的成员函数中,那你可能还想要捕获this指针来使用这个类的成员变量:
[this](){};
当然,你也可以直接使用=或者&,它们同样也会将this包含进来。
四、mutable与constexpr
默认情况下,lambda表达式捕获的变量值是不可变的:

如果你想要修改它的值,一种方式是使用引用捕获,但这样做会修改外部变量的值:

如果你仅仅只是想要在lambda内部修改使用、不影响原值,那么就可以使用mutable关键字:

此外,C++20引入了constexpr关键字,结合lambda表达式可以实现编译期间求值的功能,提高程序性能:
int main() {
	int a = 10;
	constexpr auto lambda = [](int x){
		return x * x;
	};
	static_assert(lambda(10)==100);
}
而这里使用到的static_assert是静态断言,如果在编译期间不满足填入的表达式就会直接报错,从这里我们就可以确定它是编译期间求值。
比如,如果我们填其它值,我们甚至都无法进行编译:

五、常见用途
正因为它使用起来非常的方便,我们大部分都是将其填入一些需要接收回调函数的函数参数中。
比如标准库中有一个sort函数,它可以用来排序,可如果想要排序自定义数据类型,那么就可以使用它的第三个参数传入一个回调函数,而这个回调函数最简单的方式就是使用lambda表达式:
#include<iostream>
#include<algorithm>
using namespace std;
struct Stu
{
	string name;
	int age;
};
int main() {
	Stu s[5]{
		{"小明",10},
		{"小红",50},
		{"小蓝",20},
		{"小青",90},
		{"小风",30},
	};
	sort(s, s + 5, [](Stu &a,Stu &b) {
		return a.age < b.age; //前一个小于后一个,即从小到大排序
		});
	for (int i = 0; i < 5; i++) {
		cout << s[i].name << ":" << s[i].age << endl;
	}
}
这个sort函数的前两个参数是要排序的范围,而第三个参数就是我们的lambda表达式,sort函数会将要比较的两个元素通过参数传入,也就是这里的Stu类型,然后就可以自定义排序规则了。
