14.C++ Lambda 表达式高级用法解析与实际案例

一、前言

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表达式捕获的变量值是不可变的:

image.png

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

image.png

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

image.png

此外,C++20引入了constexpr关键字,结合lambda表达式可以实现编译期间求值的功能,提高程序性能:

int main() {
	int a = 10;
	constexpr auto lambda = [](int x){
		return x * x;
	};
	static_assert(lambda(10)==100);
}

而这里使用到的static_assert是静态断言,如果在编译期间不满足填入的表达式就会直接报错,从这里我们就可以确定它是编译期间求值。

比如,如果我们填其它值,我们甚至都无法进行编译:

image.png

五、常见用途

正因为它使用起来非常的方便,我们大部分都是将其填入一些需要接收回调函数的函数参数中。

比如标准库中有一个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类型,然后就可以自定义排序规则了。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux