前言

lambda表达式是C++11新特性之一,下面一起来看一下lambda表达式。
先来看一个例子,假设有一个整数列表,我们希望统计一下其中有多少个整数可以被7整除。
第一种方案,我们先采用普通函数的形式来实现。我们使用vector来存储数字,使用generate()来往vector中填充随机数。 这里介绍一下generate()的用法,该函数接受一个区间,由前两个参数指定,并将区间中的每个元素设置为第三个参数返回的值,如下:

std::generate(v.begin(),v.end(),std::rand);

上面的代码即是往vector中填充随机数。
为判断元素是否能被7整除,可以使用下列的函数:

bool fun7(int x){return x%7 == 0;}

为了计算有多少个数能被7整除,这里采用count_if()算法,count_if()算法和generate()函数差不都,同样前两个参数也是指定了一个区间,第三个参数则是一个bool 类型的函数对象。因此,我们可以通过以下函数来计算符合条件的元素。

int num = std::count_if(v.begin(),v.end(),fun7);

完整代码如下:

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
bool fun7(int x)
{
	return x % 7== 0;
}
int main()
{
	vector<int> vec(10);
	generate(vec.begin(),vec.end(),rand);
	for(int i = 0;i < 10;i++)
		cout << vec[i] << "  ";
	cout << endl;
	int count = count_if(vec.begin(),vec.end(),fun7);
	cout << count << endl;
	return 0;
}

针对上例的第二种方案,就是采用lambda来替换fun7函数。与fun7函数对应的lambda表达式如下:

[](int x) {return x%7==0;}

因此 ,我们可以直接使用上面的表达式直接替换fun7,代码如下:

#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
//bool fun7(int x)
//{
//	return x%13==0;
//}
int main()
{
	vector<int> vec(10);
	generate(vec.begin(),vec.end(),rand);
	for(int i = 0;i<10;i++)
		cout << vec[i] << "  ";
	cout << endl;
	int count = count_if(vec.begin(),vec.end(),[](int x){return x % 7 == 0;}   //使用lambda表达式替换fun7函数
	);
	cout << count << endl;

	return 0;
}

咋一看,lambda表达式和fun7函数的定义很相像,他们的差别有两个,一个是lambda表达式没有函数名;没有声明返回类型。实际上lambda的返回类型是根据返回值推断得到的。
接下来让我们一起看一下lambda表达式是如何书写的?

lambda表达式

先来看一下lambda表达式的语法形式:

[ capture ] ( params ) opt -> ret { body; };

其中carpture是捕获列表,params是参数,opt是选项,ret则是返回值的类型,body则是函数的具体实现。
1.捕获列表描述了lambda表达式可以访问上下文中的哪些变量。
[] :表示不捕获任何变量
[=]:表示按值捕获变量
[&]:表示按引用捕获变量
[this]:值传递捕获当前的this
但是捕获列表不允许变量的重复传递:例如

[=,x]

上面这种捕获是不允许的,=表示按值的方式捕获所有的变量,x相当于被重复捕获了。
2.params表示lambda的参数,用在{}中。
3.opt表示lambda的选项,例如mutable,后面会介绍一下mutable的用法。
4.ret表示lambda的返回值,也可以显示指明返回值,lambda会自动推断返回值,但是值得注意的是只有当lambda的表达式仅有一条return语句时,自动推断才是有效的。像下面这种的表达式就需要加上返回类型。

[](double x )->double{int y = x ;return x - y;};

虽然lambda表达式是匿名函数,但是实际上也可以给lambda表达式指定一个名称,如下表示:

auto f = [](int x ){return x % 3 ==0;};

此后再需要使用该lambda表达式,就可以使用f()来代替。举一个例子:

#include <iostream>
using namespace std;
int main()
{
	int a = 5,b = 6;
	auto f = [=]{return a+b;};//[=]按值捕获了a和b
	cout << f() << endl;
	return 0;
}

运行程序输出结果为11,由上栗看出可以给lambda指定一个名称,再用该名称来取代调用该表达式。

按值捕获和按引用捕获

按值捕获和按引用捕获的用法通过下面这个例子来看一下。

#include <iostream>
using namespace std;
int main()
{
	int a = 5;
	auto f1 = [=]{return a+1;};//按值捕获a
	auto f2 = [&]{return a+1;};//按引用捕获a
	cout << f1() << endl;
	cout << f2()<< endl;
	a++;
	cout << f1() << endl;
	cout << f2() << endl;
	return 0;
}

程序运行结果如下:
在这里插入图片描述
先看第一次f1和f2的输出,都是6,这没有什么问题。再执行a++,输出f1和f2,f2的结果是7 也没有问题,为什么f1还是输出6呢?答案就是按值捕获可以理解为一旦lambda按值捕获某个变量相当于在表达式内部已经生成了一个被捕获变量的副本,而lambda表达式使用的就是这个副本,原本的变量再怎么变化都不会影响到副本的值,所以f1 lambda表达式中的值一直都是捕获时a 的值 也就是5,后续a++的操作和f1表达式没有关系。简单可以理解为f1表达式赋值了一个和a同名的const变量。从这我们也可以得出一个结论:
如果希望lambda函数在调用时访问的外部变量是最新的,我们就需要使用按引用捕获。
下面可以看一下这个例子:

#include <iostream>
using namespace std;
int main()
{
	int a = 5;
	auto f = [=]{return a*=5;};//按值捕获a
	cout << f() << endl;
	return 0;
}

运行程序,有如下编译报错:
在这里插入图片描述
提示a是一个只读的,不允许修改,这就验证上面例子中说明的按值捕获实际上是lambda拷贝了一个与被捕获变量同名的const 副本并进行操作。
如果实在需要改变lambda中的值,这时就需要使用上文提到过的选项mutable。
默认情况下,lambda函数是一个const函数,而mutable也可以取消常量性。例如有如下代码:

#include <iostream>
using namespace std;
int main()
{
	int a = 5;
	auto f = [=]()mutable{return a*=5;};//取消常量性
	cout << f() << endl;
	return 0;
}

这里需要注意,被 mutable 修饰的 lambda 表达式就算没有参数也要写明参数列表。
运行程序如下:
在这里插入图片描述
可以看到原先lambda函数中的a是只读的,加上mutable就可以修改lambda函数中a的值了。
到这,如果您觉得这篇文章给您带来了收获,希望您能动动小手点个赞,您的鼓励就是我的动力,当然如果本文哪里有写的不对的地方,也欢迎各路大佛指正。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐