目录
Lambda 函数是 C++11 标准新增的语法糖,也被称为 lambda 表达式或匿名函数。它具有距离近、简洁、高效和功能强大等特点。
示例:
[](const int& n) -> void { cout << "number:" << n << endl; };
一、语法
以下是一个包含 lambda 函数、普通函数、仿函数语法比较的示例代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 普通函数
void zsshow(const int & no) {
cout << "number:" << no << "\n";
}
// 仿函数
class czs
{
public:
void operator()(const int & no) {
cout << "number:" << no << "\n";
}
};
int main()
{
vector<int> vv = { 5,8,3 }; // 存放编号的容器
// 第三个参数是普通函数
for_each(vv.begin(), vv.end(), zsshow);
// 第三个参数是仿函数
for_each(vv.begin(), vv.end(), czs());
// 第三个参数是 lambda 表达式
for_each(vv.begin(), vv.end(),
[](const int& no) {
cout << "number:" << no << "\n";
}
);
}
二、参数列表
参数列表是可选的,类似普通函数的参数列表。如果没有参数列表,()
可以省略不写。
与普通函数的不同之处在于:
-
lambda 函数不能有默认参数。
-
所有参数必须有参数名。
-
不支持可变参数。
三、返回类型
用后置的方法书写返回类型,类似于普通函数的返回类型。如果不写返回类型,编译器会根据函数体中的代码推断出来。不过,如果有返回类型,建议显式地指定,因为自动推断可能与预期不一致。
四、函数体
类似于普通函数的函数体,和普通函数体的使用一样。
五、捕获列表
通过捕获列表,lambda 函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。捕获列表书写在[]
中,与函数参数的传递类似,捕获方式可以是值和引用。以下列出了不同的捕获列表的方式。
- 值捕获
与传递参数类似,采用值捕获的前提是变量可以拷贝。与传递参数不同,变量的值是在 lambda 函数创建时拷贝,而不是调用时拷贝。
例如:
size_t v1 = 42;
auto f = [ v1 ] { return v1; }; // 使用了值捕获,将 v1 拷贝到名为 f 的可调用对象
v1 = 0;
auto j = f(); // j 为 42,f 保存了我们创建它时 v1 的拷贝
由于被捕获的值是在 lambda 函数创建时拷贝,因此在随后对其修改不会影响到 lambda 内部的值。默认情况下,如果以传值方式捕获变量,则在 lambda 函数中不能修改变量的值。
- 引用捕获
和函数引用参数一样,引用变量的值在 lambda 函数体中改变时,将影响被引用的对象。
size_t v1 = 42;
auto f = [ &v1 ] { return v1; }; // 引用捕获,将 v1 拷贝到名为 f 的可调用对象
v1 = 0;
auto j = f(); // j 为 0
如果采用引用方式捕获变量,就必须保证被引用的对象在 lambda 执行的时候是存在的。
- 隐式捕获
除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。
隐式捕获有两种方式,分别是[=]
和[&]
。[=]
表示以值捕获的方式捕获外部变量,[&]
表示以引用捕获的方式捕获外部变量。
int a = 123;
auto f = [ = ] { cout << a << endl; }; // 值捕获
f(); // 输出:123
auto f1 = [ & ] { cout << a++ << endl; }; // 引用捕获
f1(); // 输出:123(采用了后++)
cout << a << endl; // 输出 124
- 混合方式捕获
lambda 函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。
混合捕获时,捕获列表中的第一个元素必须是 =
或 &
,此符号指定了默认捕获的方式是值捕获或引用捕获。
需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:
int i = 10;
int j = 20;
auto f1 = [ =, &i] () { return j + i; }; // 正确,默认值捕获,显式是引用捕获
auto f2 = [ =, i] () { return i + j; }; // 编译出错,默认值捕获,显式值捕获,冲突了
auto f3 = [ &, &i] () { return i +j; }; // 编译出错,默认引用捕获,显式引用捕获,冲突了
- 修改值捕获变量的值
在 lambda 函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。
在 lambda 函数中,如果希望修改值捕获变量的值,可以加 mutable
选项,但是,在 lambda 函数的外部,变量的值不会被修改。
int a = 123;
auto f = [a]()mutable { cout << ++a << endl; }; // 不会报错
cout << a << endl; // 输出:123
f(); // 输出:124
cout << a << endl; // 输出:123
- 异常说明
lambda 可以抛出异常,用 throw(…)
指示异常的类型,用 noexcept
指示不抛出任何异常。
六、lambda 函数的本质
当我们编写了一个 lambda 函数之后,编译器将它翻译成一个类,该类中有一个重载了 ()
的函数。
- 采用值捕获
采用值捕获时,lambda 函数生成的类用捕获变量的值初始化自己的成员变量。
例如:
int a =10;
int b = 20;
auto addfun = [=] (const int c ) -> int { return a+c; };
int c = addfun(b);
cout << c << endl;
等同于:
class Myclass
{
int m_a; // 该成员变量对应通过值捕获的变量
public:
Myclass( int a ) : m_a(a){}; // 该形参对应捕获的变量
// 重载了()运算符的函数,返回类型、形参和函数体都与 lambda 函数一致
int operator()(const int c) const
{
return a + c;
}
};
默认情况下,由 lambda 函数生成的类是 const
成员函数,所以变量的值不能修改。如果加上 mutable
,相当于去掉 const
。这样上面的限制就能讲通了。
- 采用引用捕获
如果 lambda 函数采用引用捕获的方式,编译器直接引用就行了。
唯一需要注意的是,lambda 函数执行时,程序必须保证引用的对象有效。
所以lambda表达式其实就是C++标准委员会把仿函数包装了一下的一个新玩法,只不过这个玩法玩得好玩得秒!使得C++代码可以更加简洁高效。