lambda表达式
[TOC]
lambda表达是c++中的可调用对象之一,在C++11中被引入到标准库中,使用时不需要包含任何头文件,但是编译时需要指定-std=c++11
或其他支持c++11标准的编译命令(比如-std=c++0x
或-std=c++14
或-std=c++1y
)。lambda表达式源于函数式编程的概念,简单来说它是一个匿名函数。它最大的作用就是不需要额外再写一个函数或者函数对象,避免了代码膨胀功能分散,让开发人员更加集中精力在手边的问题,提高生产效率。
基本概念和用法
c++中,普通的函数是长这样子的:
ret_value function_name(parameter) option { function_body; }
比如:
int get_value(int a) const {return a++;}
lambda表达式定义了一个匿名函数,并且可以捕获所定义的匿名函数外的变量。它的语法形式是:
[ capture ] ( parameter ) option -> return_type { body; };
其中:
- capture 捕获列表
- parameter 参数列表
- option 函数选项
- return_type 函数返回值类型
- body 函数体
比如:
// defination
auto lamb = [](int a) -> int { return a++; };
// usage
std::cout << lamb(1) << std::endl; // output: 2
组成lambda的各部分并不是都必须存在的:
- 当编译器可以推导出返回值类型的时候,可以省略返回值类型的部分
auto lamb = [](int i){return i;}; // OK, return type is int
auto lamb2 = [] () {return {1, 2};}; // Error
- lambda表达式没有参数时,参数列表可以省略
auto lamb = []{return 1;}; // OK
所以一个最简单的lambda表达式可以是下面这样,这段代码是可以通过编译的:
int main()
{
[]{}; // lambda expression
return 0;
}
捕获列表
捕获列表是lambda表达式和普通函数区别最大的地方。[]
内就是lambda表达式的捕获列表。一般来说,lambda表达式都是定义在其他函数内部,捕获列表的作用,就是使lambda表达式内部能够重用所有的外部变量。捕获列表可以有如下的形式:
-
[]
不捕获任何变量 -
[&]
以引用方式捕获外部作用域的所有变量 -
[=]
以赋值方式捕获外部作用域的所有变量 -
[=, &foo]
以赋值方式捕获外部作用域所有变量,以引用方式捕获foo变量 -
[bar]
以赋值方式捕获bar变量,不捕获其它变量 -
[this]
捕获当前类的this指针,让lambda表达式拥有和当前类成员同样的访问权限,可以修改类的成员变量,使用类的成员函数。如果已经使用了&或者=,就默认添加此选项。
捕获列表示例:
#include <iostream>
class TLambda
{
public:
TLambda(): i_(0) { }
int i_;
void func(int x, int y) {
int a;
int b;
// 无法访问i_, 必须捕获this,正确写法见l4
auto l1 = [] () {
return i_;
};
// 以赋值方式捕获所有外部变量,这里捕获了this, x, y
auto l2 = [=] () {
return i_ + x + y;
};
// 以引用方式捕获所有外部变量
auto l3 = [&] () {
return i_ + x + y;
};
auto l4 = [this] () {
return i_;
};
// 以为没有捕获,所以无法访问x, y, 正确写法是l6
auto l5 = [this] () {
return i_ + x + y;
};
auto l6 = [this, x, y] () {
return i_ + x + y;
};
auto l7 = [this] () {
return ++i_;
};
// 错误,没有捕获a变量
auto l8 = [] () {
return a;
};
// a以赋值方式捕获,b以引用方式捕
auto l9 = [a, &b] () {
return a + (++b);
};
// 捕获所有外部变量,变量b以引用方式捕获,其他变量以赋值方式捕获
auto l10 = [=, &b] () {
return a + (++b);
}
}
};
int main()
{
TLambda a;
a.func(3, 4);
return 0;
}
引用和赋值,就相当于函数参数中的按值传递和引用传递。如果lambda捕获的变量在外部作用域改变了,以赋值方式捕获的变量则不会改变。按值捕获的变量在lambda表达式内部也不能被修改,如果要修改,需使用引用捕获,或者显示的指定lambda表达式为 mutable
。
// [=]
int func(int a);
// [&]
int func(int& a);
// ------------------------------------------
int a = 0;
auto f = [=] {return a;}; // 按值捕获
a += 1; // a被修改
cout << f() << endl; // output: 0
// ------------------------------------------
int a = 0;
auto f = [] {return a++;}; // Error
auto f = [] () mutable {return a++;}; // OK
lambda表达式的类型
上面一直使用auto
关键字来自动推导lambda表达式的类型,那作为强类型语言的c++,这个lambda表达式到底是什么类型呢?lambda的表达式类型在c++11中被称为『闭包类型(Closure Tyep)』,是一个特殊的、匿名的、非联合(union)、非聚合(aggregate)的类类型。可以认为它是一个带有operator()
的类,即防函数(functor)。因此可以使用std::function
和std::bind
来存储和操作lambda表达式。
std::function<int (int)> f = [](int a) {return a;};
std::function<int (int)> f = std::bind([](int a){return a;}, std::placeholders::_1);
std::cout << f(22) << std::endl;
对于没有捕获任何变量的lambda,还可以转换成一个普通的函数指针。
using func_t = int (*)(int);
func_t f = [](int a){return a;};
std::cout << f(22) << std::endl;
参考文档: