资讯动态

【C++】C++11lambda/包装器

发布时间:2026/10/23 2:54:55 来源:尧图企业网站定制
1lambda1.1lambda表达式语法• lambda 表达式本质是⼀个匿名函数对象跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。• lambda表达式的格式 [capture-list] (parameters)- return type {function boby }• [capture-list] :捕捉列表该列表总是出现在 lambda 函数的开始位置编译器根据[]来判断接下来的代码是否为 lambda 函数捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使⽤捕捉列表可以传值和传引⽤捕捉具体细节7.2中我们再细讲。捕捉列表为空也不能省略。• (parameters) 参数列表与普通函数的参数列表功能类似如果不需要参数传递则可以连同()⼀起省略• -return type 返回值类型⽤追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。⼀般返回值类型明确情况下也可省略由编译器对返回类型进⾏推导。• {function boby} 函数体函数体内的实现跟普通函数完全类似在该函数体内除了可以使⽤其参数外还可以使⽤所有捕获到的变量函数体为空也不能省略。intmain(){// ⼀个简单的lambda表达式autoadd1[](intx,inty)-int{returnxy;};coutadd1(1,2)endl;// 1、捕捉为空也不能省略// 2、参数为空可以省略// 3、返回值可以省略可以通过返回对象⾃动推导// 4、函数题不能省略autofunc1[]{couthello bitendl;return0;};func1();inta0,b1;autoswap1[](intx,inty){inttmpx;xy;ytmp;};swap1(a,b);couta:bendl;return0;}1.2捕捉列表• lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量如果想⽤外层作⽤域中的变量就需要进⾏捕捉• 第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉捕捉的多个变量⽤逗号分割。[xyz]表⽰x和y值捕捉z引⽤捕捉。• 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉我们在捕捉列表写⼀个表⽰隐式值捕捉在捕捉列表写⼀个表⽰隐式引⽤捕捉这样我们 lambda 表达式中⽤了那些变量编译器就会⾃动捕捉那些变量。• 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[,x]表⽰其他变量隐式值捕捉x引⽤捕捉[,x,y]表⽰其他变量引⽤捕捉x和y值捕捉。当使⽤混合捕捉时第⼀个元素必须是或并且混合捕捉时后⾯的捕捉变量必须是值捕捉同理混合捕捉时后⾯的捕捉变量必须是引⽤捕捉。• lambda 表达式如果在函数局部域中他可以捕捉 lambda 位置之前定义的变量不能捕捉静态局部变量和全局变量静态局部变量和全局变量也不需要捕捉 lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置捕捉列表必须为空。• 默认情况下 lambda 捕捉列表是被const修饰的也就是说传值捕捉的过来的对象不能修改mutable加在参数列表的后⾯可以取消其常量性也就说使⽤该修饰符后传值捕捉的对象就可以修改了但是修改还是形参对象不会影响实参。使⽤该修饰符后参数列表不可省略(即使参数为空)。2包装器2.1 functionintx0;// 捕捉列表必须为空因为全局变量不⽤捕捉就可以⽤没有可被捕捉的变量autofunc1[](){x;};intmain(){// 只能⽤当前lambda局部域和捕捉的对象和全局对象inta0,b1,c2,d3;autofunc1[a,b]{// 值捕捉的变量不能修改引⽤捕捉的变量可以修改//a;b;intretab;returnret;};coutfunc1()endl;// 隐式值捕捉// ⽤了哪些变量就捕捉哪些变量autofunc2[]{intretabc;returnret;};coutfunc2()endl;// 隐式引⽤捕捉// ⽤了哪些变量就捕捉哪些变量autofunc3[]{a;c;d;};func3();couta b c dendl;// 混合捕捉1autofunc4[,a,b]{//a;//b;c;d;returnabcd;};func4();couta b c dendl;// 混合捕捉1autofunc5[,a,b]{a;b;/*c; d;*/returnabcd;};func5();couta b c dendl;// 局部的静态和全局变量不能捕捉也不需要捕捉staticintm0;autofunc6[]{intretxm;returnret;};// 传值捕捉本质是⼀种拷⻉,并且被const修饰了// mutable相当于去掉const属性可以修改了// 但是修改了不会影响外⾯被捕捉的值因为是⼀种拷⻉autofunc7[]()mutable{a;b;c;d;returnabcd;};coutfunc7()endl;couta b c dendl;return0;}1.3 lambda的应⽤• 在学习 lambda 表达式之前我们的使⽤的可调⽤对象只有函数指针和仿函数对象函数指针的类型定义起来⽐较麻烦仿函数要定义⼀个类相对会⽐较麻烦。使⽤ lambda 去定义可调⽤对象既简单⼜⽅便。• lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑智能指针中定制删除器等 lambda 的应⽤还是很⼴泛的以后我们会不断接触到。structGoods{string _name;// 名字double_price;// 价格int_evaluate;// 评价// ...Goods(constchar*str,doubleprice,intevaluate):_name(str),_price(price),_evaluate(evaluate){}};structComparePriceLess{booloperator()(constGoodsgl,constGoodsgr){returngl._pricegr._price;}};structComparePriceGreater{booloperator()(constGoodsgl,constGoodsgr){returngl._pricegr._price;}};intmain(){vectorGoodsv{{苹果,2.1,5},{⾹蕉,3,4},{橙⼦,2.2,3},{菠萝,1.5,4}};// 类似这样的场景我们实现仿函数对象或者函数指针⽀持商品中// 不同项的⽐较相对还是⽐较⿇烦的那么这⾥lambda就很好⽤了sort(v.begin(),v.end(),ComparePriceLess());sort(v.begin(),v.end(),ComparePriceGreater());sort(v.begin(),v.end(),[](constGoodsg1,constGoodsg2){returng1._priceg2._price;});sort(v.begin(),v.end(),[](constGoodsg1,constGoodsg2){returng1._priceg2._price;});sort(v.begin(),v.end(),[](constGoodsg1,constGoodsg2){returng1._evaluateg2._evaluate;});sort(v.begin(),v.end(),[](constGoodsg1,constGoodsg2){returng1._evaluateg2._evaluate;});return0;}1.4 lambda的原理• lambda 的原理和范围for很像编译后从汇编指令层的⻆度看压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器⽽lambda底层是仿函数对象也就说我们写了⼀个lambda 以后编译器会⽣成⼀个对应的仿函数的类。• 仿函数的类名是编译按⼀定规则⽣成的保证不同的 lambda ⽣成的类名不同lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体 lambda 的捕捉列表本质是⽣成的仿函数类的成员变量也就是说捕捉列表的变量都是 lambda 类构造函数的实参当然隐式捕捉编译器要看使⽤哪些就传那些对象。2. 包装器2.1 functiontemplateclassTclassfunction;// undefinedtemplateclassRet,class...ArgsclassfunctionRet(Args...);• std::function 是⼀个类模板也是⼀个包装器。 std::function 的实例对象可以包装存储其他的可以调⽤对象包括函数指针、仿函数、 lambda 、 bind 表达式等存储的可调⽤对象被称为 std::function 的⽬标 。若 std::function 不含⽬标则称它为 空 。调⽤空std::function 的 ⽬标 导致抛出std::bad_function_call异常。• 以上是 function 的原型他被定义头⽂件中。std::function-cppreference.com是function的官⽅⽂件链接。• 函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同 std::function 的优势就是统⼀类型对他们都可以进⾏包装这样在很多地⽅就⽅便声明可调⽤对象的类型下⾯的第⼆个代码样例展⽰了 std::function 作为map的参数实现字符串和可调⽤对象的映射表功能。includefunctionalintf(inta,intb){returnab;}structFunctor{public:intoperator()(inta,intb){returnab;}};classPlus{public:Plus(intn10):_n(n){}staticintplusi(inta,intb){returnab;}doubleplusd(doublea,doubleb){return(ab)*_n;}private:int_n;};intmain(){// 包装各种可调⽤对象functionint(int,int)f1f;functionint(int,int)f2Functor();functionint(int,int)f3[](inta,intb){returnab;};coutf1(1,1)endl;coutf2(1,1)endl;coutf3(1,1)endl;// 包装静态成员函数// 成员函数要指定类域并且前⾯加才能获取地址functionint(int,int)f4Plus::plusi;coutf4(1,1)endl;// 包装普通成员函数// 普通成员函数还有⼀个隐含的this指针参数所以绑定时传对象或者对象的指针过去都可以functiondouble(Plus*,double,double)f5Plus::plusd;Plus pd;coutf5(pd,1.1,1.1)endl;functiondouble(Plus,double,double)f6Plus::plusd;coutf6(pd,1.1,1.1)endl;coutf6(pd,1.1,1.1)endl;functiondouble(Plus,double,double)f7Plus::plusd;coutf7(move(pd),1.1,1.1)endl;coutf7(Plus(),1.1,1.1)endl;return0;}2.2 bindsimple(1)templateclassFn,class...Args/* unspecified */bind(Fnfn,Args...args);withreturntype(2)templateclassRet,classFn,class...Args/* unspecified */bind(Fnfn,Args...args);• bind 是⼀个函数模板它也是⼀个可调⽤对象的包装器可以把他看做⼀个函数适配器对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在这个头⽂件中。• 调⽤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的⼀个命名空间中。#includefunctionalusingplaceholders::_1;usingplaceholders::_2;usingplaceholders::_3;intSub(inta,intb){return(a-b)*10;}intSubX(inta,intb,intc){return(a-b-c)*10;}classPlus{public:staticintplusi(inta,intb){returnab;}doubleplusd(doublea,doubleb){returnab;}};intmain(){autosub1bind(Sub,_1,_2);coutsub1(10,5)endl;// bind 本质返回的⼀个仿函数对象// 调整参数顺序不常⽤// _1代表第⼀个实参// _2代表第⼆个实参// ...autosub2bind(Sub,_2,_1);coutsub2(10,5)endl;// 调整参数个数 常⽤autosub3bind(Sub,100,_1);coutsub3(5)endl;autosub4bind(Sub,_1,100);coutsub4(5)endl;// 分别绑死第123个参数autosub5bind(SubX,100,_1,_2);coutsub5(5,1)endl;autosub6bind(SubX,_1,100,_2);coutsub6(5,1)endl;autosub7bind(SubX,_1,_2,100);coutsub7(5,1)endl;// 成员函数对象进⾏绑死就不需要每次都传递了functiondouble(Plus,double,double)f6Plus::plusd;Plus pd;coutf6(move(pd),1.1,1.1)endl;coutf6(Plus(),1.1,1.1)endl;// bind⼀般⽤于绑死⼀些固定参数functiondouble(double,double)f7bind(Plus::plusd,Plus(),_1,_2);coutf7(1.1,1.1)endl;// 计算复利的lambdaautofunc1[](doublerate,doublemoney,intyear)-double{doubleretmoney;for(inti0;iyear;i){retret*rate;}returnret-money;};// 绑死⼀些参数实现出⽀持不同年华利率不同⾦额和不同年份计算出复利的结算利息functiondouble(double)func3_1_5bind(func1,0.015,_1,3);functiondouble(double)func5_1_5bind(func1,0.015,_1,5);functiondouble(double)func10_2_5bind(func1,0.025,_1,10);functiondouble(double)func20_3_5bind(func1,0.035,_1,30);coutfunc3_1_5(1000000)endl;coutfunc5_1_5(1000000)endl;coutfunc10_2_5(1000000)endl;coutfunc20_3_5(1000000)endl;return0;}

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑