C++ Lambda 学习笔记
C++ 中,对于一个对象或一个表达式,如果可以对其使用调用运算符(()
),则称它是可调用的。即,如果 e
是可调用的,则可以这样使用:
1 | e(args) |
其中,args
是一个逗号分隔的一个或多个参数的列表。
C++ 中可调用对象除了我们熟悉的函数或函数指针外,还包括函数对象以及 lambda 表达式。
本文重点讲述 lambda 表达式。
lambda 表达式表示一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数。一个 lambda 表达式具有如下形式:
1 | [capture list](parameter list) -> return type {function body} |
- capture list:捕获列表,lambda 表达式所在函数中定义的局部变量列表
- parameter list:参数列表
- return type:返回类型
- function body:函数体
lambda 表达式可以忽略参数列表和返回类型,但必须包含捕获列表和函数体:
1 | auto f = [] {return 10;}; |
上述代码中,我们定义了一个可调用对象 f
,它不接受参数,返回 10。
lambda 的调用方式与普通函数的调用方式一样:
1 | cout << f() << endl; |
输出:
10
向 lambda 传递参数
lambda 可以带上参数,例如:
1 | [](const string &s1, const string &s2) { |
上述定义的 lambda 带上两个 const string &
类型的参数,lambda 函数体比较两个 string
的长度并返回 bool
值。
可以使用此 lambda 来调用 std::stable_sort
排序。当 std::stable_sort
需要比较两个元素时,它就会调用指定的这个 lambda。
1 |
|
输出:
lee
hao
leehao
leehao.me
lambda 捕获列表
一个 lambda 可以使用其所在函数中的局部变量,但需要明确指明这些变量。lambda 捕获列表可以用来声明这些需要使用的局部变量。例如:
1 | [sz](const string &s) { |
由于此 lambda 捕获 sz
,因此,可以在函数体中使用 sz
。如果未捕获 sz
,则会报编译错误:
1 | // 编译错误:sz 未捕获 |
lambda 本质上是函数对象。当向一个函数参数传递一个 lambda 时,同时定义了一个新的函数对象类型并生成一个此类型的未命名对象。
因此,从 lambda 生成的函数对象类型包含此 lambda 所捕获的变量的数据成员,且 lambda 的数据成员在 lambda 对象创建时被初始化。
值捕获
类似参数传递,变量的捕获方式可以是值或引用。与传递参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在 lambda 创建时拷贝,而不是调用时拷贝:
1 | void fcn1() |
输出:
42
由于被捕获变量的值是在 lambda 创建时拷贝,因此,随后对其修改不会影响到 lambda 内对应的值。
引用捕获
定义 lamda 时可以采用引用方式捕获变量:
1 | void fcn2() |
输出:
0
v1
前的 &
表示 v1
以引用方式捕获。当我们在 lambda 函数体内使用此变量,实际上使用的是引用所绑定的对象。因此,当 v1
在修改后,对象 f2
引用的 v1
也发生修改。
当以引用方式捕获一个变量时,必须保证在 lambda 执行时,变量是存在的。
lambda 返回类型
上述例子中,我们并没有为 lambda 指定返回类型,这是由于编译器可以正常推断出 lambda 的返回类型。
例如,使用标准库算法 transform
和一个 lambda 来将整型向量中每个负数转化为其绝对值:
1 | vector<int> vi {1, -2, 3, 2, -4, 5}; |
transform
第 4 个参数是一个 lambda。
lambda 函数体是单一的 return
语句,我们没有指定返回类型,这是由于编译器可以根据条件运算符来推断出返回类型。
也可以指定 lambda 的返回类型:
1 | vector<int> vi {1, -2, 3, 2, -4, 5}; |
transform
第 4 个参数是一个 lambda,它的捕获列表为空,接受一个 int 参数,返回一个 int 值。
可以输出 vi
元素的值:
1 | for (auto i : vi) { |
输出:
1
2
3
2
4
5
参考资料
- 《C++ Primer》,第五版,中文版