C++高阶-lambda表达式

Lambda 表达式

在 C++中,我们用 Lambda 表达式来表达匿名函数。所谓匿名函数,就是这个函数只有函数体、参数和返回值类型,而没有函数名。

  • 在作用上, Lambda表达式跟函数指针和函数对象相同,它同样可以方便地应用于 STL 算法中,对算法进行自定义。
  • 在使用上, Lambda 表达式可以在使用函数的地方对其进行定义,使整个代码更加自然流畅。
  • 更方便的是,可以在 Lambda 表达式中直接访问 Lambda 表达式之外的数据,这样就避免了复杂的参数传递,也解决了函数执行过程中状态数据的保存问题,为数据的操作带来很大的便利。
// 定义变量,用于保存函数执行过程中的状态数据
int nTotalHeight = 0;
int nCount = 0;
// 在 for_each()算法中使用 Lambda 表达式,统计身高
for_each( vecStu.begin(), vecStu.end(),
[&](const Student& st) // Lambda 表达式
{
	nTotalHeight += st.GetHeight(); // 直接访问外部数据,累计身高
	++nCount;
});
if( 0 != nCount )
{
	cout<<nCount<<"个学生的平均身高是: "
	<<(float)nTotalHeight/nCount<<endl;
}

在 for_each()算法原来放置函数指针或函数对象的第三个参数位置,使用一对中括号“[ ]”来表示一个 Lambda 表达式的开始其后的“(const Student& st)”就是这个表达式的参数。因为返回值无关紧要,我们这里省略了 Lambda 表达式的返回值类型。

当 for_each()算法循环遍历容器中的每一个 Student 对象时,它就会将 Student 对象作为参数调用 Lambda 表达式,从而将 Student 对象传递到 Lambda 内部。因为“[]”中括号中是“&”符号,所以在 Lambda 表达式内部可以以引用的形式直接访问外部的任何变量,所以我们用成员函数获得 Student 对象的身高并直接累加到 nTotalHeight 变量,实现了函数执行过程中状态数据的保存。 for_each()算法执行完毕后,nTotalHeight 和 nCount 都已经保存了统计的数据,进行简单的计算并将结果输出就“优雅”地完成了整个统计工作。

Lambda 表达式的定义与使用

在 C++中定义一个Lambda 表达式的语法格式如下:

[变量使用说明符号](参数列表) -> 返回值数据类型
{
// 函数体
}

中括号“[ ]”表示 Lambda 表达式的开始,用来告诉编译器接下来的代码就是 Lambda 表达式。

捕捉方式

在中括号中,可以指定 Lambda 表达式对当前作用域(也就是 Lambda 所在的大括号“{}”范围)中的变量的捕捉方式,所以这个中括号也可以称为“捕捉列表( capture list) ”。

[=]传值复制

如果我们希望在Lambda表达式内部以传值(复制)的方式使用当前作用域中的所有变量,则使用“[=]”表示,这就意味着 Lambda表达式内访问到的变量只是外部同名变量的一个副本,在 Lambda 表达式内部对变量的修改不会影响到外部的同名变量。换句话说,也就是在 Lambda 内部只能读取外部变量的值但却没法对其进行修改。如果试图修改,将导致一个编译错误。如果中括号留空,默认情况下也表示以传值方式使用 Lambda表达式外部的变量。例如:

vector<int> v = {51,82,73,44,58};
int nAdd = 10;
// 为容器中小于 60 的分数加上 10 分
for_each(v.begin(), v.end(),
// “[=]”表示以传值的方式使用 Lambda 外部的变量
// 因为要修改容器中的数据,所以参数采用引用形式
[=](int& x)
{
	nAdd = 20; // 试图修改外部变量会导致编译错误
	if(x < 60)
	{
		x += nAdd; // 只读访问 nAdd
	}
});
[&]引用修改

如果想在 Lambda 表达式内部对外部变量进行修改,则可以使用“[&]”代替“[=]”作为 Lambad 表达式的开始,这表示 Lambda 表达式将以传引用的方式捕捉当前作用域内的变量。这就意味着 Lambda表达式内部的变量都是外部同名变量的引用,所以在 Lambda 表达式中对这些变量的修改将直接影响到当前作用域中变量本身。

int nTotal = 0;
for_each(v.begin(), v.end(),
[&](int x) // “[&]”表示以传引用的方式使用 Lambda 外部的变量
{
	nTotal += x; // 修改变量的值
});
cout<<"容器中数据的总和是: "<<nTotal<<endl;
多种方式共存

如果需要与 Lambda 表达式传递多个数据,而同时各个数据的传递方式又各不相同,那么可以在中括号中的第一个位置用“&”作为 Lambda 表达式的默认传递方式,而那些需要以传值方式进行传递的变量,则可以单独在中括号中列出。例如,我们希望在统计成绩的同时修正成绩:

int nAdd = 10;
int nTotal = 0;
for_each(v.begin(), v.end(),
[&, nAdd](int& x) // 默认采用传引用访问, nAdd 使用传值访问
{
	if( x < 60 )
	{
		x += nAdd; // 传值访问 nAdd,只能读取
	}
	nTotal += x; // 默认采用传引用访问 nTotal,可以写入
});
cout<<"容器中所有数据的总和是"<<nTotal<<endl;

参数列表

在 Lambda 表达式用“[]”中括号对捕捉变量的方式进行说明后,就是它的参数列表。它的参数列表跟普通函数的参数列表类似,主要用于接收 STL 算法传递进来的数据,所以其参数的个数由具体的算法决定,而参数的类型则由容器中所保存数据的类型决定,至于参数的传递形式,到底是传值还是传引用,则由我们是否需要修改容器中的数据决定。

返回值->

在完成参数列表的定义后,接下来就是 Lambda 表达式的返回值类型了。在大多数情况下,Lambda 表达式所表达的只是对数据的简单处理,不需要返回值,这时可以省略返回值类型的定义。如果某些算法需要 Lambda 表达式有返回值,则可以在参数列表后使用“->”符号来定义它的返回值类型。

// 统计容器中的及格分数 
int nPass = count_if(vecScore.begin(),vecScore.end(),
[=](int x) -> bool // 定义 Lambda 表达式的返回值类型为 bool 类型
{
	// 判断分数是否及格
	return x > 60;
});

用function 类模版定义可以使用 Lambda 表达式的函数

function 类模版表示一个函数。当我们以某种返回值类型以及参数个数和类型特化这个类模板后,得到的是可以代表相应类型函数的模板类。然后使用这个模板类类型作为函数的参数类型,这个函数就可以接受拥有相应返回值和参数的函数指针或者函数对象为实际参数,进而可以在函数内部调用这些通过 function 类型参数传递进来的函数以实现对函数的自定义。
从本质上讲, Lambda 表达式的实质也是一个函数。既然 function 类型的参数可以接受函数指针和函数对象,自然也可以接受相应类型的 Lambda 表达式。

#include <functional> // 引入 function 类模板所在的头文件
// 可以接受 Lambda 表达式的 mycount_if()算法
int mycount_if(const vector<int>& v, // 需要统计的容器
// 将函数指针类型更换为 function<bool(int)>类型,
// 表示它可以接受一个返回值为 bool 类型,同时拥有一个 int 类型参数
// 的函数指针或函数对象,自然也可以是相应类型的 Lambda 表达式
function<bool(int)> is)
{
	// 函数体无需进行任何修改…
}
// …
// 在 mycount_if()算法中应用 Lambda 表达式
int nPass = mycount_if(vecScore,
// 一个返回值为 bool 类型,同时拥有一个 int 类型参数的 Lambda 表达式
[=](int x) -> bool
{
	return x >= 60; // 判断分数是否及格
});

借助 function 类模板,我们也能定义可以使用 Lambda 表达式的函数,从而将函数的部分业务逻辑留给函数的使用者去灵活地实现,使之可以适应更多的需求,大大地增加了函数的通用性。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值