1、综述
lambda表达式是一个匿名函数,它可以在函数内部定义,编译器会将lambda表达式当做一个函数对象。lambda表达式的形式为:[捕获列表] (参数列表) -> 返回类型{ 函数体 },其中“捕获列表”是表达式所在函数中定义的局部变量的列表,可以为空,而且lambda必须使用尾置返回来指定函数返回类型,eg:
int main() { function<int(int)> func = [](int x)->int { return x * x; }; cout << func(5) << endl; auto Func = [](int x)->int { return x * x; }; cout << Func(10) << endl; int(*FuncPtr)(int) = Func; cout << (*FuncPtr)(20) << endl; return 0; }
lambda表达式的函数返回类型也可以省略,这时候lambda根据函数体中return语句自动推断出返回类型,如果函数体中没有return语句的话则会认为返回类型为void,代码示例如下。有些情况下,lambda不能自动推断出返回类型,比如函数体内含有多个return语句(if(x) return i; else return -1;),这个时候编译会出错,所以最保险的是不省略返回类型。
function<int(int)> func = [](int x)/*->int*/{ return x * x; }; auto Func = [](int x){ return x * x; };
如果lambda表达式的没有参数的话还可以忽略参数列表,eg
function<int()> func = [] { return 100; }; cout << func() << endl; auto Func = [] { return 100; }; cout << Func() << endl; return 0;
“捕获列表”是lambda表达式所在函数中定义的局部变量的列表,它用来指出在表达式内部可以使用这些变量。“捕获列表”中的变量只能是局部变量,且不能static类型,对于static类型的局部变量,表达式内部可以直接使用,而且static类型变量是引用捕获。前面说过编译器会将lambda表达式当做一个函数对象,所以“捕获列表”中的变量相当于是将其保存在了函数对象中。eg:
int main() { int i = 10; int j = 10; static int k = 0; auto Func = [i, j](int x) { k; return x * i * j; }; cout << Func(5) << endl; //输出500 return 0; }
lambda表达式可以用在STL算法中,如下代码展示了sort()算法分别使用函数指针,函数对象,lambda表达式的示例:
bool myFunction(const int& i, const int& j) { return i > j; } class myClass { public: bool operator() (const int& i, const int& j) { return i > j; } }; int main() { vector<int> v = list_of(5) (2) (4) (3) (1) (5); sort(v.begin(), v.end(), myFunction); sort(v.begin(), v.end(), myClass()); sort(v.begin(), v.end(), [](const int& i, const int& j){ return i > j; }); for (auto iter = v.begin(); iter != v.end(); iter++) cout << *iter << endl; return 0; }
2、再谈变量的捕获
“捕获列表”中的变量的捕获也可以是值捕获或引用捕获,如果是值捕获的话,传给捕获列表的变量在Lambda中实际上为变量的一个副本,而且被捕获的变量的值是在lambda表达式创建的时候就拷贝,而不是像函数参数的值传递那样在调用时拷贝,而且采用值捕获的变量如果会在函数体内修改的话还要使用mutable关键字来声明方法,eg:
int main() { int n = 10; auto func = [n]()mutable{ return ++n;}; //lambda函数体内会修改n,所以应该加mutable关键字 n = 0; auto num = func(); //n是lambda创建的时候拷贝而不是调用的时候拷贝,所以num是11而不是0 return 0; }
如果是引用捕获的话在“捕获列表”中的捕获变量需要加&,eg:
int main() { int n = 10; auto func = [&n](){ return ++n;}; //n是引用捕获 n = 0; auto num = func(); //lambda调用的时候n为0,执行完lambda函数体后,num值为1,n为1 return 0; }
我们也可以不传递“捕获列表”,让编译器根据lambda体中的变量使用代码来推断出哪些变量是捕获变量,即隐式捕获。为了指示由编译器推断捕获变量,应该在捕获列表中写一个=或&,=表示使用值引用方式,&表示采用引用捕获方式。如果我们希望对一部分变量采用值捕获,另一部分变量采用引用捕获,还可以混合使用隐式捕获和显示捕获。eg:
int main()
{
int n = 10;
auto func1 = [=]()mutable{ return ++n;}; //n是隐式值捕获
auto func2 = [&]() {return ++n;}; //n是隐式引用捕获
int i = 1, j = 2;
auto func3 = [=, &i, &j]() {return n * i * j;}; //n是隐式值捕获,i和j是显示引用捕获
auto func4 = [&, i, j]() {return n * i * j;}; //n是隐式引用捕获,i和j是显示值捕获
return 0;
}
可以在捕获列表中传入当前类的指针this,这样就可以在lamda中直接使用当前类的成员函数和成员变量:
class Foo { public: Foo() { Bar* b = new Bar; b->onClick = [this]{ cout << "onClick" << endl; func(); }; } void func(){} }; class Bar { public: std::function<void()> onClick; };
捕获总结:
1)、[]不捕获任何变量。
2)、[bar]按值捕获bar变量。
3)、[&foo]按引用捕获foo变量。
4)、[=]捕获外部作用域中所有变量,并作为副本在函数体中使用,即按值捕获。
5)、[&]捕获外部作用域中所有变量,并作为引用在函数体中使用,即按引用捕获。
6)、[=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。
7)、[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限,如果已经使用了&或者=,就默认添加此选项。
3、lambda与函数
对于那些只在一两个地方使用的简单操作,可以直接使用lambda表达式。如果一个操作会被多个地方会使用,或者操作很复杂(包含多条语句),最好还是定义一个函数来使用。
lambda表达式捕获变量的功能与bind绑定参数有相似的效果。
对于子函数可以使用父函数中的局部变量这种行为,我们可以称做“闭包”,即“闭包”就是能够读取其他函数(父函数)中内部变量的函数(子函数),通常就是定义在一个函数内部的函数。可见lambda表达式和bind绑定参数都可以实现“闭包”。