一、语法
1.基础语法
[capture](paramLists) mutable ->retunType{statement}
- capture。捕获列表,用于捕获前文的变量供lambda函数中使用,可省略。
- (paramLists)。参数列表,可省略。
- mutable。lambda表达式默认具有常量性,可以通过mutable取消常量性,可省略。
- returnType。函数返回类型,可省略。
- statement。函数体,可省略。
结合上述可省略的内容,C++11中最简单的lambda表达式可以是(虽然没有实际意义):
[]{}
2.捕获列表
lambda函数的与普通函数最大的区别在于可以捕获前文的局部变量(仅仅对于局部而言,如果是全局lambda函数则不支持)。而捕获的方式有:
- [var]表示值传递方式捕获变量var
- [=]表示值传递方式捕获父作用域所有变量(包括this)
- [&var]表示引用传递方式捕获变量var
- [&]表示引用传递方式捕获父作用域所有变量(包括this)
- [this]表示值传递方式捕获变量this
而由于捕获列表支持多个值(用,分隔),因此可以进行组合:
- [=,&a,&b]表示引用传递捕获a,b,值传递捕获其他内容。
- [&,a,this]表示值传递捕获a,this,引用传递捕获其他内容。
需要注意的是,捕获列表不能重复,如
[=,a,b]或者[&,&a,&b]等都是重复捕获(以相同的传递方式捕获同一个变量)。
3.基础使用
lambda函数通常用于局部作用域作为局部[匿名]函数。
extern int z;
extern float c;
void Calc(int& , int, float &, float);
void TestCalc() {
int x, y = 3;
float a, b = 4.0;
int success = 0;
auto validate = [&]() -> bool
{
if ((x == y + z) && (a == b + c))
return 1;
else
return 0;
};
Calc(x, y, a, b);
success += validate();
y = 1024;
b = 1e13;
Calc(x, y, a, b);
success += validate();
}
// 编译选项:g++ -c -std=c++11 7-3-7.cpp
而在有时会通过auto为lambda函数命名,使其获得自说明性。
与普通函数相比lambda有如下优势:
- 支持直接在函数内创建,作用域外释放,而不用额外创建一个函数。
- 能够直接捕获所有局部变量,而普通函数则需要额外传递。
- lambda函数默认内联,在较多次调用时性能比普通函数好。
- lambda函数的设计更简单,不需要考虑参数传递等问题
二、关于lambda的一些实验与讨论
1.捕获参数的传递方式
lambda函数中不同的捕获传递方式会造成不同的结果,对于值传递,则在传递的值在编译期就确定了,无法被修改,而对于引用传递则可以同步lambda函数外的修改。
#include <iostream>
using namespace std;
int main() {
int j = 12;
auto by_val_lambda = [=] { return j + 1;};
auto by_ref_lambda = [&] { return j + 1;};
cout << "by_val_lambda: " << by_val_lambda() << endl;
cout << "by_ref_lambda: " << by_ref_lambda() << endl;
j++;
cout << "by_val_lambda: " << by_val_lambda() << endl;
cout << "by_ref_lambda: " << by_ref_lambda() << endl;
}
运行结果:
by_val_lambda: 13
by_ref_lambda: 13
by_val_lambda: 13
by_ref_lambda: 14
2.与函数指针的关系
lambda函数与函数指针看起来很相似,但是实际上却不是函数指针,它是一种称为"闭包"(closure)的类。
这种类型支持向函数指针转换,前提是:
-
lambda函数不捕获任何变量
-
函数指针的原型与lambda一致(参数,返回值都完全一致)
int main() { int girls = 3, boys = 4; auto totalChild = [](int x, int y)->int{ return x + y; }; typedef int (*allChild)(int x, int y); typedef int (*oneChild)(int x); allChild p; p = totalChild; oneChild q; q = totalChild; // 编译失败,参数必须一致 decltype(totalChild) allPeople = totalChild; // 需通过decltype获得lambda的类型 decltype(totalChild) totalPeople = p; // 编译失败,指针无法转换为lambda return 0; } // 编译选项:g++ -std=c++11 7-3-10.cpp
此外,不支持函数指针向lambda转换
3.常量性与mutable
前面提到对于值传递的捕获参数具有常量性无法被修改,而想要打破这一限制,可以加上mutable关键字。(注意虽然可以修改,但仍然不影响父作用域变量)
#include <iostream>
int main() {
int val=0;
// 编译失败, 在const的lambda中修改常量
//auto const_val_lambda = [=]() { val = 3; };
// 非const的lambda,可以修改常量数据
auto mutable_val_lambda = [=]() mutable { val = 3; };
mutable_val_lambda();
std::cout << val << std::endl;
// 依然是const的lambda,不过没有改动引用本身
auto const_ref_lambda = [&] { val = 4; };
const_ref_lambda();
std::cout << val << std::endl;
// 依然是const的lambda,通过参数传递val
auto const_param_lambda = [&](int v) { v = 5; };
const_param_lambda(val);
std::cout << val << std::endl;
return 0;
}
而对于引用传递方式,则表示lambda捕获的参数引用了父作用域的变量,一边修改都会同步到另一边。
三、lambda与STL
前面说到,lambda对C++11最大的贡献,或者说是改变,应该在STL库中。这主要体现于STL算法更加容易,也更加容易学习了(可读性更高)。
下面将以for_each为例,讲述lambda带来的便捷。
#include <vector>
#include <algorithm>
using namespace std;
vector<int> nums;
vector<int> largeNums;
const int ubound = 10;
inline void LargeNumsFunc(int i){
if (i > ubound)
largeNums.push_back(i);
}
void Above() {
// 传统的for循环
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
if (*itr >= ubound)
largeNums.push_back(*itr);
}
// 使用函数指针
for_each(nums.begin(), nums.end(), LargeNumsFunc);
// 使用lambda函数和算法for_each
for_each(nums.begin(), nums.end(), [=](int i){
if (i > ubound)
largeNums.push_back(i);
});
}
编译选项: g++ 7-3-13.cpp -c -std=c++11
这是通过基础for循环、for_each和lambda实现查找大于某个值的功能。相比for循环而言,for_each只需要关心数据起始点,并将每个元素作用到指定的操作上即可,在效率、正确性、可维护性上都具有一定优势。
而lambda较for_each而言,首先其函数内容会直接放在调用处,可阅读性更高(当然,有时也会被分离出来并命名,但通常不会太远);其次使用函数指针很可能导致编译器不对其进行inline优化(inline对编译器而言并非强制),在循环次数较多的时候,内联的lambda和没有能够内联的函数指针可能存在着巨大的性能差别。
此外相较于仿函数(不论是自己实现还是内置仿函数),lambda也依旧存在着不小的优势。
#include <vector>
#include <algorithm>
using namespace std;
vector<int> nums;
vector<int> largeNums;
class LNums{
public:
LNums(int u): ubound(u){}
void operator () (int i) const
{
if (i > ubound)
largeNums.push_back(i);
}
private:
int ubound;
};
void Above(int ubound) {
// 传统的for循环
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
if (*itr >= ubound)
largeNums.push_back(*itr);
}
// 使用仿函数
for_each(nums.begin(), nums.end(), LNums(ubound));
// 使用lambda函数和算法for_each
for_each(nums.begin(), nums.end(), [=](int i){
if (i > ubound)
largeNums.push_back(i);
});
}
对于自己实现的仿函数,很直观的,lambda更加简洁。
而当面对更加复杂的场景时,lambda显得更加有优势:
#include <vector>
#include <algorithm>
using namespace std;
extern vector<int> nums;
void TwoCond(int low, int high) {
// 传统的for循环
for (auto i = nums.begin(); i != nums.end(); i++)
if (*i >= low && *i < high) break;
// 利用了3个内置的仿函数,以及非标准的compose2
find_if(nums.begin(), nums.end(),
compose2(logical_and<bool>(),
bind2nd(less<int>(), high),
bind2nd(greater_equal<int>(), low)));
// 使用lambda函数
find_if(nums.begin(), nums.end(), [=](int i) {
return i >= low && i < high;
});
}
这里我们需找到vector nums中第一个值介于[low, high)间的元素,可以看到内置仿函数变得异常复杂。