一、lambda表达式1.lambda表达式语法lambda表达式本质是一个匿名函数对象和普通函数不同的是他可以定义在函数内部。lambda 表达式语法使用层而言没有类型所以我们一般是用 auto 或者模板参数定义的对象去接 收 lambda对象。lambda表达式的格式[ capture-list ] (parameters)- return type { function boby }[ capture-list] :捕捉列表该列表总是出现在 lambda 函数的开始位置编译器根据[]来判断接下来的代码是否为 lambda 函数捕捉列表能够捕捉上下文中的变量供 lambda 函数使用捕捉列表可以传值和传引用捕捉具体细节下面细讲。捕捉列表为空也不能省略。( parameters) 参数列表与普通函数的参数列表功能类似如果不需要参数传递则可以连 同()一起省略-return type 返回值类型用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。一般返回值类型明确情况下也可省略由编译器对返回类型进行推导。{ function boby} 函数体函数体内的实现跟普通函数完全类似在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量函数体为空也不能省略。int main() { auto add1 [](int x, int y)-int {return x y;}; cout add1(1, 2) endl; // 1.捕捉为空也不能省略 // 2.参数为空可以省略 // 3.返回值可以省略可以通过返回对象自动推导 // 4.函数体不能省略 auto func1 [] { cout hello word endl; return 0; }; func1(); return 0; }2.捕捉列表• lambda 表达式中默认只能用 lambda 函数体和参数中的变量如果想用外层作用域中的变量就需要进行捕捉•第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉捕捉的多个变量用逗号分割。[x yz]表示 x 和 y 传值捕捉z 引捕捉。• 第二种捕捉方式是在捕捉列表中隐式捕捉我们在捕捉列表写一个 表示隐式值捕捉在捕捉列表写一个 表示隐式引用捕捉这样我们 lambda 表达式中用了哪些变量编译器就会自动捕捉哪些变量。• 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[,x]表示其他变量隐式值捕捉 x引用捕捉[,x,y]表示其他变量引用捕捉x和y 传值捕捉。当使用混合捕捉时第一个元素必须是 或 并且 混合捕捉时后面的捕捉变量必须是值捕捉同理 混合捕捉时后面的捕捉变量必须是引用捕捉。• lambda 表达式如果在函数局部域中他可以捕捉 lambda 位置之前定义的变量不能捕捉静态局部变量和全局变量静态局部变量和全局变量也不需要捕捉 lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置捕捉列表必须为空。•默认情况下 lambda 捕捉列表是被 const 修饰的也就是说传值捕捉的过来的对象不能修改 mutable 加在参数列表的后面可以取消其常量性也就是说使用该修饰符后传值捕捉的对象就可以修改了但是修改还是形参对象不会影响实参。使用该修饰符后参数列表不可省略(即使参数为空)。int x 0; int main() { // 只能用当前lambda局部域和捕捉的对象和全局对象 int a 0, b 1, c 2, d 3; auto func1 [a, b]() { // 值捕捉的变量不能修改引用捕捉的变量可以修改 // a; b; x; int ret a b; return ret; }; cout func1() endl; // 隐式值捕捉 // 用了哪些变量就捕捉哪些变量 auto func2 [] { int ret a b c; return ret; }; cout func2() endl; // 隐式引用捕捉 // 用了哪些变量就捕捉哪些变量 auto func3 [] { a; c; d; }; func3(); cout a b c d endl; // 混合捕捉 auto func4 [, a, b] { //a; //b; c; d; return a b c d; }; func4(); cout a b c d endl; // 混合捕捉 auto func5 [, a, b] { a; b; /*c; d;*/ return a b c d; }; func5(); cout a b c d endl; return 0; }3.lambda的应用在学习 lambda 表达式之前我们使用的可调用对象只有函数指针和仿函数对象函数指针的类型定义起来比较麻烦仿函数要定义一个类相对会比较麻烦。使用 lambda 去定义可调用对象既方便又简单lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑智能指针中定制删除器等 lambda 的应用还是很广泛的。#includevector #includealgorithm struct Goods { string _name; // 名字 double _price; // 价格 int _evaluate; // 评价 // ... Goods(const char* str, double price, int evaluate) :_name(str) , _price(price) , _evaluate(evaluate) { } }; struct Compare1 { bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; } /*bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; }*/ }; struct Compare2 { bool operator()(const Goods gl, const Goods gr) { return gl._price gr._price; } }; struct Compare3 { bool operator()(const Goods gl, const Goods gr) { return gl._evaluate gr._evaluate; } }; int main() { vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2, 3}, { 菠萝, 1.5, 4 } }; sort(v.begin(), v.end(), Compare1()); sort(v.begin(), v.end(), Compare2()); sort(v.begin(), v.end(), Compare3()); auto priceLess [](const Goods gl, const Goods gr) { return gl._price gr._price; }; sort(v.begin(), v.end(), priceLess); sort(v.begin(), v.end(), [](const Goods gl, const Goods gr){ return gl._price gr._price; }); sort(v.begin(), v.end(), [](const Goods gl, const Goods gr) { return gl._evaluate gr._evaluate; }); sort(v.begin(), v.end(), [](const Goods gl, const Goods gr) { return gl._evaluate gr._evaluate; }); }4.lambda的原理lambda 的原理和范围for很像编译后从汇编指令层的角度看压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器而 lambda 底层是仿函数对象也就说我们写了一个lambda 以后编译器会生成一个对应的仿函数的类。仿函数的类名是编译按一定规则生成的保证不同的 lambda 生成的类名不同lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体 lambda 的捕捉列表本质是生成的仿函数类的成员变量也就是说捕捉列表的变量都是 lambda 类构造函数的实参当然隐式捕捉编译器要看使用哪些对象就传哪些对象。• 上面的原理我们可以透过汇编层了解一下下面汇编层代码印证了上面的原理。class Rate { public: Rate(double rate) : _rate(rate) {} double operator()(double money, int year) { return money * _rate * year; } private: double _rate; }; int main() { double rate 0.49; // lambda auto r2 [rate](double money, int year) { return money * rate * year; }; // 函数对象 Rate r1(rate); r1(10000, 2); r2(10000, 2); }// 汇编代码 // lambda auto r2 [rate](double money, int year) { return money * rate * year; }; 00007FF70CE51A2B lea rdx,[rate] 00007FF70CE51A2F lea rcx,[r2] 00007FF70CE51A33 call main::2::lambda_1::lambda_1 (07FF70CE517B0h) 00007FF70CE51A38 nop // 函数对象 Rate r1(rate); 00007FF70CE51A39 movsd xmm1,mmword ptr [rate] 00007FF70CE51A3E lea rcx,[r1] 00007FF70CE51A42 call Rate::Rate (07FF70CE510C8h) 00007FF70CE51A47 nop r1(10000, 2); 00007FF70CE51A48 mov r8d,2 00007FF70CE51A4E movsd xmm1,mmword ptr [__real40c3880000000000 (07FF70CE5ACD0h)] 00007FF70CE51A56 lea rcx,[r1] 00007FF70CE51A5A call Rate::operator() (07FF70CE51136h) 00007FF70CE51A5F nop r2(10000, 2); 00007FF70CE51A60 mov r8d,2 00007FF70CE51A66 movsd xmm1,mmword ptr [__real40c3880000000000 (07FF70CE5ACD0h)] 00007FF70CE51A6E lea rcx,[r2] 00007FF70CE51A72 call main::2::lambda_1::operator() (07FF70CE51870h) 00007FF70CE51A77 nop }汇编层可以看到 r2 lambda 对象调用本质还是调用 operator() 类型是 lambda_1, 这个类型名的规则是编译器自己定制的保证不同的 lambda 不冲突二、包装器1.function// 官方文档 template class T class function; //undefined template class Ret, class... Args class functionRet(Args...);• std::function 是一个类模板也是一个包装器。 std::function 的实例对象可以包装存储其他的可以调用的对象包括函数指针、仿函数、 lambda 、bind 表达式等存储的可调用对象被称为 std::function 的目标。若 std::function不含目标则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call(std::bad_function_call - cppreference.com) 异常。• 以上是 function 的原型他被定义functional头件中。std::function - cppreference.com是function的官方文件链接。• 函数指针、仿函数、 lambda 等可调用对象的类型各不相同 std::function 的优势就是统一类型对他们都可以进行包装这样在很多地方就方便声明可调用对象的类型下面的第二个代 码样例展示了 std::function 作为map的参数实现字符串和可调用对象的映射表功能。#include functional #include vector using namespace std; int f(int a, int b) { return a b; } struct Functor { public: int operator() (int a, int b) { return a b; } }; class Plus { public: Plus(int n 10) :_n(n) {} static int plusi(int a, int b) { return a b; } double plusd(double a, double b) const { return (a b) * _n; } private: int _n; }; int main() { // void(*pf)(int) nullptr; // // 包装各种可调用对象 functionint(int, int) f1 f; functionint(int, int) f2 Functor(); functionint(int, int) f3 [](int a, int b) {return a b; }; cout f1(1, 1) endl; cout f2(1, 1) endl; cout f3(1, 1) endl; vectorfunctionint(int, int) vf { f , Functor(),[](int a, int b) {return a b; } }; for (auto f : vf) { cout f(1, 1) endl; } // 包装静态成员函数 // 成员函数要指定类域并且前面加才能获取地址,一般取地址 functionint(int, int) f4 Plus::plusi; cout f4(1, 1) endl; functiondouble(Plus*, double, double) f5 Plus::plusd; Plus ps; cout f5(ps, 1.1, 1.1) endl; //functiondouble(Plus, double, double) f6 Plus::plusd; functiondouble(const Plus, double, double) f6 Plus::plusd; cout f6(Plus(), 1.1, 1.1) endl; cout f6(ps, 1.1, 1.1) endl; functiondouble(Plus, double, double) f7 Plus::plusd; cout f7(Plus(), 1.1, 1.1) endl; cout f7(move(ps), 1.1, 1.1) endl; return 0; }150. 逆波兰表达式求值 - 力扣LeetCode// 常规代码 class Solution { public: int evalRPN(vectorstring tokens) { stackint st; for(int i 0; i tokens.size(); i) { const string s tokens[i]; if(s ! s ! - s ! * s ! /) { st.push(stoi(s)); } else { int right st.top(); st.pop(); int left st.top(); st.pop(); switch(s[0]) { case : st.push(left right); break; case -: st.push(left - right); break; case *: st.push(left * right); break; case /: st.push(left / right); break; } } } return st.top(); } };下面进行优化使用 map 映射 string 和 function 的方式实现这种方式的最大优势之一是方便扩展假设还有其他运算我们增加 map 中的映射即可class Solution { public: int evalRPN(vectorstring tokens) { stackint st; unordered_mapstring, functionint(int, int) opFuncMap { {,[](int x, int y){return x y;}}, {-,[](int x, int y){return x - y;}}, {*,[](int x, int y){return x * y;}}, {/,[](int x, int y){return x / y;}} }; for(int i 0; i tokens.size(); i) { const string s tokens[i]; if(opFuncMap.count(s)) { int right st.top(); st.pop(); int left st.top(); st.pop(); int ret opFuncMap[s](left, right); st.push(ret); } else { st.push(stoi(s)); } } return st.top(); } };2.bind• bind 是一个函数模板它也是一个可调用对象的包装器可以把他看做一个函数适配器对接收的 fn 可调用对象进行处理后返回一个可调用对象。 bind 可以用来调整参数个数和参数顺序。bind 也在function这个头文件中。• 调用 bind 的一般形式auto newCallable bind(callable,arg_list);其中 newCallable本身是一个可调用对象arg_list 是一个逗号分隔的参数列表对应给定的 callable的参数。当我们调用newCallable时newCallable会调用callable并传给它arg_list中的参数。arg_list中的参数可能包含形如 _n 的名字其中n是一个整数这些参数是占位符表示 newCallable 的参数它们占据了传递给 newCallable 的参数的位置。数值 n 表示生成的可调用对象中参数的位置_1 为newCallable的第一个参数_2为第二个参数以此类推。_1 / _ 2 / _3 ....这些占位符放到placeholders的一个命名空间中。看下面代码来理解吧其实用起来是很简单的。#includefunctional using placeholders::_1; using placeholders::_2; using placeholders::_3; int Sub(int a, int b) { return (a - b) * 10; } int SubX(int a, int b, int c) { return (a - b - c) * 10; } class Plus { public: static int plusi(int a, int b) { return a b; } double plusd(double a, double b) { return a b; } }; int main() { cout Sub(10, 5) endl; // bind 本质返回的一个仿函数对象 // 调整参数顺序不常用 // _1代表第一个实参 // _2代表第二个实参 // ... auto newSub1 bind(Sub, _1, _2); auto newSub1 bind(Sub, _2, _1); cout newSub1(10, 5) endl; // 调整参数个数 auto newSub2 bind(Sub, _1, 10); cout newSub2(20) endl; auto newSub3 bind(Sub, 5, _1); cout newSub3(20) endl; auto newSub4 bind(SubX, 5, _1, _2); cout newSub4(10, 20) endl; auto newSub5 bind(SubX, _1, 5, _2); cout newSub5(10, 20) endl; auto newSub6 bind(SubX, _1, _2, 5); cout newSub6(10, 20) endl; // 成员函数对象进行绑死就不需要每次都传递了 //functiondouble(Plus, double, double) f6 Plus::plusd; //Plus pd; //cout f6(move(pd), 1.1, 1.1) endl; //cout f6(Plus(), 1.1, 1.1) endl; //// auto f7 bind(Plus::plusd, Plus(), _1, _2); //functiondouble(double, double) f7 bind(Plus::plusd, Plus(), _1, _2); //cout f7(1.1, 1.1) endl; functiondouble(Plus, double, double) f6 Plus::plusd; Plus pd; cout f6(move(pd), 1.1, 1.1) endl; cout f6(Plus(), 1.1, 1.1) endl; //auto f7 bind(Plus::plusd, _1, _2); functiondouble(double, double) f7 bind(Plus::plusd,Plus(), _1, _2); cout f7(1.1, 1.1) endl; // 100w // 计算复利的lambda auto func1 [](double rate, double money, int year)-double { double ret money; for (int i 0; i year; i) { ret ret * rate; } return ret - money; }; //cout func1(0.2, 1000000, 3) endl; //cout func1(0.2, 1000000, 10) endl; //cout func1(0.2, 1000000, 30) endl; // 绑死一些参数实现出支持不同年华利率不同金额和不同年份计算出复利的结算利息 functiondouble(double) func3_1_5 bind(func1, 0.015, _1, 3); functiondouble(double) func5_2_0 bind(func1, 0.02, _1, 5); functiondouble(double) func10_2_5 bind(func1, 0.025, _1, 10); functiondouble(double) func20_3_5 bind(func1, 0.035, _1, 30); cout func3_1_5(1000000) endl; cout func5_2_0(1000000) endl; cout func10_2_5(1000000) endl; cout func20_3_5(1000000) endl; return 0; }