7.3 lambda函数

一、语法

1.基础语法

复制代码
[capture](paramLists) mutable ->retunType{statement}
  • capture。捕获列表,用于捕获前文的变量供lambda函数中使用,可省略。
  • (paramLists)。参数列表,可省略。
  • mutable。lambda表达式默认具有常量性,可以通过mutable取消常量性,可省略。
  • returnType。函数返回类型,可省略。
  • statement。函数体,可省略。

结合上述可省略的内容,C++11中最简单的lambda表达式可以是(虽然没有实际意义):

复制代码
[]{}

2.捕获列表

lambda函数的与普通函数最大的区别在于可以捕获前文的局部变量(仅仅对于局部而言,如果是全局lambda函数则不支持)。而捕获的方式有:

  • var\]表示值传递方式捕获变量var

  • \&var\]表示引用传递方式捕获变量var

  • this\]表示值传递方式捕获变量this

  • =,\&a,\&b\]表示引用传递捕获a,b,值传递捕获其他内容。

需要注意的是,捕获列表不能重复,如

=,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 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 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 #include using namespace std; vector nums; vector 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 #include using namespace std; vector nums; vector 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 #include using namespace std; extern vector 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(), bind2nd(less(), high), bind2nd(greater_equal(), low))); // 使用lambda函数 find_if(nums.begin(), nums.end(), [=](int i) { return i >= low && i < high; }); } 这里我们需找到vector nums中第一个值介于\[low, high)间的元素,可以看到内置仿函数变得异常复杂。

相关推荐
月亮有痕迹诶1 天前
【C++】智能指针
开发语言·c++·c++11
egoist20231 天前
【C++指南】一文总结C++二叉搜索树
开发语言·数据结构·c++·c++11·二叉搜索树
郭源潮14 天前
《C++11:通过thread类编写C++多线程程序》
开发语言·c++·c++11
结衣结衣.7 天前
【Qt】QWidget的styleSheet属性
开发语言·qt·c++11
1024熙9 天前
【C++】——C++11新特性
c语言·开发语言·c++·c++11
修修修也12 天前
【C++11】左值引用、右值引用、移动语义和完美转发
开发语言·c++·学习·c++11
郭源潮119 天前
《 线程池项目:线程池背景知识与整体架构梳理》
c++·线程池·c++11·c++17
结衣结衣.19 天前
【Qt】QWidget属性介绍
开发语言·c++·qt·c++11
结衣结衣.22 天前
【Qt】带参数的信号和槽函数
开发语言·qt·c++11
结衣结衣.25 天前
【Qt】自定义信号和槽函数
开发语言·c++·qt·c++11