当前位置: 首页 > news >正文

C++11:lambda表达式,包装器

一、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, y,&z]表示 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 的应用还是很广泛的。

#include<vector>
#include<algorithm>
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()
{vector<Goods> 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;// lambdaauto r2 = [rate](double money, int year) {return money * rate * year;};// 函数对象Rate r1(rate);r1(10000, 2);r2(10000, 2);
}

// 汇编代码:// lambdaauto 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 [__real@40c3880000000000 (07FF70CE5ACD0h)]  
00007FF70CE51A56  lea         rcx,[r1]  
00007FF70CE51A5A  call        Rate::operator() (07FF70CE51136h)  
00007FF70CE51A5F  nop  r2(10000, 2);
00007FF70CE51A60  mov         r8d,2  
00007FF70CE51A66  movsd       xmm1,mmword ptr [__real@40c3880000000000 (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;   //undefinedtemplate <class Ret, class... Args>
class function<Ret(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;// // 包装各种可调用对象function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(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;vector<function<int(int, int)>> vf = { f , Functor(),[](int a, int b) {return a + b; } };for (auto& f : vf){cout << f(1, 1) << endl;}// 包装静态成员函数// 成员函数要指定类域并且前面加&才能获取地址,一般取地址function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus ps;cout << f5(&ps, 1.1, 1.1) << endl;//function<double(Plus, double, double)> f6 = &Plus::plusd;function<double(const Plus&, double, double)> f6 = &Plus::plusd;cout << f6(Plus(), 1.1, 1.1) << endl;cout << f6(ps, 1.1, 1.1) << endl;function<double(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(vector<string>& tokens) {stack<int> 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(vector<string>& tokens) {stack<int> st;unordered_map<string, function<int(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的一个命名空间中。

看下面代码来理解吧,其实用起来是很简单的。

#include<functional>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;// 成员函数对象进行绑死,就不需要每次都传递了//function<double(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);//function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);//cout << f7(1.1, 1.1) << endl;function<double(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);function<double(double, double)> f7 = bind(&Plus::plusd,Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;// 100w// 计算复利的lambdaauto 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;// 绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_2_0 = bind(func1, 0.02, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(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;
}

http://www.dtcms.com/a/606134.html

相关文章:

  • 方圆网 网站建设营销策略包括哪些内容
  • 勇闯前后端:后端(Python)——Week1
  • 百度伐谋正式发布!“自我演化”超级智能体,为产业难题寻找“全局最优解”
  • 什么是分布式?什么是微服务?什么是集群?什么是单体?这些都是什么?又有什么关联?
  • SQL 实战:用户访问 → 下单 → 支付全流程转化率分析
  • wordpress数据库修改主题seo体系网站的建设及优化
  • 批量更新 JAR 内配置文件的通用 Bash 脚本
  • 第四十一篇:正则表达式(Regex)终极指南:语法、re模块与实战案例
  • 建设网站用新域名还是老域名网络热词2022
  • 人称代词和物主代词
  • 16、alertmanager告警路由
  • Prim 与 Kruskal 算法在最小生成树中的应用
  • php视频网站开发实战企业怎么做app网址
  • redis不能获取连接,Could not get a resource from the pool
  • 做百度推广网站咱们做春节网页制作素材
  • Oracle 中的物理备份
  • 做服装网站需要什么条件3d模拟装修设计软件
  • 如何在手机上开自己的网站建行app怎么注册登录
  • 跨平台Hybrid App开发实战指南
  • 网站开发struts瑞昌网络推广
  • winfrom 自定义空间 ,UcCustomDataGridView
  • Spring Boot环境配置
  • 新版Xsens Link可穿戴式动捕设备
  • 淘客网站如何做推广莱芜网站建设案例
  • Linux 上怎么跑 Python 脚本
  • 微服务污点分析
  • 科学小制作 小发明 简单 手工网站seo策划方案
  • 手搓UEFI.h
  • MySQL(六) - 视图管理
  • R语言在线编译器 | 提供快速便捷的编程环境,助力数据分析与学习