lambda 表达式

基本用法

lambda 表达式是 C++11 最常用的特性之一,这是现代编程语言的一个特点,lambda 表达式的优点:
(1)声明式的编程风格:匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
(2)简洁:避免了代码膨胀和功能分散,让开发更加高效。
(3)在需要的时间和地点实现功能闭包,使程序更加灵活。
lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。

lambda 表达式的语法形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[capture](params) opt -> ret { body; };
/*
捕获列表[]:
捕获一定范围内的变量
参数列表():
和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。
opt 选项:
不需要可以省略
mutable: 可以修改按值传递进来的拷贝(注意是修改拷贝,而不是值本身)
exception: 指定函数抛出的异常使用 throw(),如抛出整数类型的异常
返回值类型:
在 C++11 中,lambda 表达式的返回值是通过返回值后置语法来定义的。
函数体:
函数的实现,这部分不能省略,但函数体可以为空。
*/

其中 capture 是捕获列表,params 是参数列表,opt 是函数选项,ret 是返回值类型,body 是函数体。

捕获列表

lambda 表达式的捕获列表可以捕获一定范围内的变量,使用方式如下:
[]:不捕捉任何变量
[&]:捕获外部作用域中所有变量,并作为引用在函数体内使用(按引用捕获)
[=]:捕获外部作用域中所有变量,并作为副本在函数体内使用(按值捕获),拷贝的副本在匿名函数体内部是只读的
[=, &foo]:按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
[bar]:按值捕获 bar 变量,同时不捕获其他变量
[&bar]:按引用捕获 bar 变量,同时不捕获其他变量
[this]:捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数相同的访问权限。如果已经使用了 & 或者 = 默认添加此选项。

使用初始化列表的用法,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
void output(int x, int y)
{
// auto x1 = [] {return m_number; }; // error:没有捕获外部变量,不能使用类成员 m_number
auto x2 = [=] { return m_number + x + y; }; // ok:以值拷贝的方式捕获所有外部变量
auto x3 = [&] { return m_number + x + y; }; // ok:以引用的方式捕获所有外部变量
auto x4 = [this] { return m_number; }; // ok:捕获 this 指针,可访问对象内部成员
// auto x5 = [this] { return m_number + x + y; }; // error:捕获 this 指针,可访问类内部成员,没有捕获到变量 x、y,因此不能访问。
auto x6 = [this, x, y] { return m_number + x + y; }; // ok:捕获 this 指针、x、y
auto x7 = [this] { return m_number++; }; // ok:捕获 this 指针,并且可以修改对象内部变量的值
}

int m_number = 100;
};

在匿名函数内部,需要通过 lambda 表达式的捕获列表控制如何捕获外部变量,以及访问哪些变量。默认状态下 lambda 表达式无法修改通过复制方式捕获外部变量,如果希望修改这些外部变量,需要通过引用的方式进行捕获。

返回值

很多时候 lambda 表达式的返回值是非常明显的,因此在 C++11 中允许省略 lambda 表达式的返回值。

一般情况下,不指定 lambda 表达式的返回值,编译器会根据 return 语句自动推导返回值的类型。

1
2
3
4
5
6
7
8
9
10
11
// 完整的 lambda 表达式定义
auto f = [](int a) -> int
{
return a + 10;
};

// 忽略返回值的 lambda 表达式定义
auto f = [](int a)
{
return a + 10;
};

需要注意的是 lambda 表达式不能通过列表初始化自动推导出返回值类型。

1
2
3
4
5
6
7
8
9
10
11
// ok:可以自动推导出返回值类型
auto f = [](int i)
{
return i;
}

// error:不能推导出返回值类型
auto f1 = []()
{
return {1, 2}; // 基于列表初始化推导返回值,错误
}

函数

使用 lambda 表达式捕获列表捕获外部变量,如果希望修改按值捕获的外部变量,那么应该如何处理呢?
使用 mutable 选项,被 mutable 修饰的 lambda 表达式没有参数也要写明参数列表,并且去掉按值捕获的外部变量的只读 const 属性。

1
2
3
int a = 0;
auto f1 = [=] { return a++; }; // error:按值捕获外部变量,a 是只读的
auto f2 = [=]() mutable { return a++; }; // ok

为什么通过值拷贝的方式捕获的外部变量是只读的?
(1)lambda 表达式的类型在 C++11 中被看做是一个带 operator()的类(仿函数)。
(2)按照 C++ 标准,lambda 表达式的 operator() 默认是 const 的,一个 const 成员函数是无法修改成员变量值的。
mutable 选项的作用就在于取消 operator()const 属性。

lambda 表达式在 C++ 中被看做是一个仿函数,因此可以使用 std::functionstd::bind 存储和操作 lambda 表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <functional>
using namespace std;

int main()
{
// 包装可调用函数
std::function<int(int)> f1 = [](int a) { return a; };
// 绑定可调用函数
std::function<int(int)> f2 = bind([](int a) { return a; }, placeholders::_1);

// 函数调用
cout << f1(100) << endl;
cout << f2(200) << endl;

return 0;
}

对于没有捕获任何变量的 lambda 表达式,可以转换成一个普通的函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <functional>

using namespace std;
using func_ptr = int(*)(int);

// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
return a;
};

int main()
{
// 函数调用
int t = f(1314);
cout << t << endl;

return 0;
}

参考资料

https://subingwen.cn/cpp/lambda/


lambda 表达式
https://lcf163.github.io/2021/09/14/lambda表达式/
作者
乘风的小站
发布于
2021年9月14日
许可协议