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

购车网站开发数据库er图wordpress更新缓存的插件

购车网站开发数据库er图,wordpress更新缓存的插件,人才引进从事网站建设,陕西城乡建设部网站首页lambda表达式是什么#xff1f;包装器又是什么#xff1f;有什么作用#xff1f;莫急#xff0c;此篇文章将详细带你探讨它们的作用。很多同学在学习时害怕这些东西#xff0c;其实都是方便使用的工具#xff0c;很多情况下我们学这些新的东西觉得麻烦#xff0c;累赘包装器又是什么有什么作用莫急此篇文章将详细带你探讨它们的作用。很多同学在学习时害怕这些东西其实都是方便使用的工具很多情况下我们学这些新的东西觉得麻烦累赘其实是实践太少了。初学编程时C语言基础不扎实代码没敲过多少项目没做过。听着别人说C/Java/Python比C语言功能强大多了赶紧去学然而除了多记一堆的语法规则也没有感觉到好用在哪里 归根到底实践太少你没有感受过一个工具带给你的不便怎会感受到另一个更好工具带给你的舒适呢刚学的东西还没有实践消化立马就去学下一个最后所获寥寥消耗不多的热情所以请实践起来 函数指针的不便   现在有这样一个场景有进行加法和减法运算的函数add(), sub()但是我们不能直接调用这两个函数而是用统一接口fun_call来调用这两个函数你只需要给出要运算的数字和要进行哪种操作函数名有fun_call在内部调用并把结果返回给你 int add(int a, int b){return a b;}int sub(int a, int b){return a - b;}int fun_call(int tmp1, int tmp2, int(*op)(int, int)){return op(tmp1, tmp2);}要实现这种统一调用接口那我们就得有三个参数两个是待操作的数字另一个是进行某种操作的函数的指针代码实现如上op就是这个函数指针 程序确实可以运行但函数指针的写法实在不好用看到函数指针这个参数你可能都要分析半天如果你觉得还好那么请看下面这个例子出自C陷阱与缺陷 (*(void(*)())0)() 你能分析出这是个什么类型的函数指针吗 我们根据()的优先级可将这个表达式分离成两部分 第一部分(* tmp )() 里面的内容用tmp代替 第二部分tmp (void(*)())0 先看第二部分void(*)()是一个返回值为void无参的函数指针类型 那(void(*)())0的意思就是将0强制转换成返回值为void无参的函数指针 为什么要这么做呢我们把强制类型转换给去掉再看一下完整的表达式如下 (*0)()该语句意思是用0这个地址作为函数指针来调用函数但是0不能被直接作为函数指针来使用 所以我们要把0强制转换成一个函数指针这样0就能被作为一个函数指针来使用了 如此以来就出现(*  (void(*)())0  )()这个看起来非常复杂的式子 事实上在操作系统以及C/C标准库中会出现大量的这种函数指针调用如果基础不扎实的话很容易就会被绕的晕头转向 仿函数的出现  鉴于函数指针这种写者难受读者也难受的境况C推出一个名为仿函数的工具仿函数笔者有在C专栏中专门提到过这里就简单的提一提不再赘述 仿函数的解决方法就是把函数封装在类里面并在类中重载()这样示例化出一个对象就可以直接当成函数来使用了在把函数作为参数传递时也不需传函数指针了而是把这个类的对象传过去就可以了如此以来就摆脱了分析函数指针的痛苦 class ADD {public:int operator()(int a, int b){return a b;}};class SUB {public:int operator()(int a, int b){return a - b;}};templateclass T int fun_call(int tmp1, int tmp2, T op){return op(tmp1, tmp2);}如上述代码把add函数和sub函数分别封装在两个类中形成两个仿函数接下来就可以通过实例化出对象来调用这两个函数传参的时候呢也是直接传对象过去 lambda的出现  仿函数的出现一定程度上减轻了传函数指针的痛苦但是这种痛苦是由分析C语言复杂的函数指针转换成对函数进行类封装的繁琐每写一个简单功能的函数就得封装成一个类用着用着大家就觉得仿函数还是不够方便 于是C11就推出了lambda表达式lambda表达式并不是C独创的而是其它语言先提出来的C委员会的人觉得lambda表达式用起来很方便就借鉴到C中了所以lambda表达式看起来并不像C的语法风格但习惯后用起来还是很方便的 lambda书写格式[capture-list] (parameters) mutable - return-type { statement} [capture-list] : 捕捉列表该列表总是出现在lambda函数的开始位置编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用 (parameters)参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略 mutable默认情况下lambda函数总是一个const函数mutable可以取消其常量性。使用该修饰符时参数列表不可省略(即使参数为空) -returntype返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。返回值类型明确情况下也可省略由编译器对返回类型进行推 {statement}函数体在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量 是不是看起来挺复杂的其实这么多东西只有捕捉列表和函数体是必须的其他则可以根据自己的需求选择性增添所以最简单的lambda表达式就是 []{} 表示什么都不做 接下来笔者逐步演示lambda表达式该如何使用还拿我们前面的add和sub函数来举例 int main(){int val_1 20;int val_2 30;auto fun [](int v1, int v2) {return v1 v2;}int result fun(val_1, val_2);cout result endl;return 0;} 先来分析[ ](int v1, int v2) {return v1 v2;} [ ]用来告诉编译器这是一个lambda表达式当然也有别的作用我们后面再提 (int v1, int v2)就是参数列表这个和普通函数的参数列表是一样的接收传过来的参数 {return v1v2;} 这个就是函数体也就是函数具体要干什么事我们这里要干的事就是将参数列表中的v1和v2相加然后返回 其实把[ ]去掉可以看出lambda表达式就是一个函数定义嘛只不过这个函数没有函数名和返回值的类型但实际上lambda表达式的底层实现和仿函数一样也是一个类这也是为什么我们用auto fun来接收lambda表达式 但是请注意lambda表达式虽然是一个类但是没有具体的类名这一点和仿函数很不一样因为仿函数的类是我们自己封装的所以我们知道具体的类名 但是lambda表达式是由编译器负责将其封装成一个类编译器为了保证其唯一性用随机哈希码来代表其类名所以我们是不知道其具体的类名的必须用auto来推导 这样看其实也能够很好理解lambda表达式我们不是嫌弃仿函数自己手动封装类太麻烦了嘛lambda表达式就是让编译器自动帮我们封装我们只要把函数的具体实现传过去就可以了不过lambda表达式的功能比仿函数还是要强大很多的我们后面慢慢说 先看看上面程序的运行结果  lambda表达式会自动帮我们推导返回值的类型所以我们没有在lambda表达式中加上返回值的类型如果你非想要指定具体的返回值类型使用表达式中 -returntype参数  上述代码在lambda表达式中加上了-double表明指定返回值类型为double类型从result变量auto的结果可以看出确实为double类型 接下来看看lambda表达式强于仿函数的功能看下列代码及运行结果  int main(){int val_1 20;int val_2 30;auto fun [val_1, val_2]{return val_1 val_2; };int result fun();cout result endl;return 0;} 什么情况这个lambda表达式竟然可以直接使用main函数内的变量val_1, val_2 别慌这就是捕捉列表的作用仔细看可以发现笔者在[ ]里加上了变量名 val_1, val_2这表示捕捉父作用域的变量 val_1, val_2父作用域指包含lambda函数的语句块也就是说一些情况下lambda表达式可以不用传参而直接使用捕捉如下是关于捕捉列表的相关写法 捕捉列表描述了上下文中哪些数据可以被lambda使用以及使用的方式传值还是传引用 咱们看看以引用的形式捕捉 int main(){int val_1 20;int val_2 30;cout 交换前val_1 val_1 val_2 val_2 endl;auto fun [val_1, val_2] {std::swap(val_1, val_2); };fun();cout 交换后val_1 val_1 val_2 val_2 endl;return 0;} 使用捕捉列表需要注意的相关事项 1.捕捉列表不允许变量重复传递否则就会导致编译错误 如代码[, val_1]{}已经使用 全部传值捕捉后面又传值捕捉val_1此时会编译报错 2.传值捕捉可以和传引用捕捉配合使用 如代码[, val_1] {}表示除val_1传引用捕捉外其余都传值捕捉  3.在块作用域以外的lambda函数捕捉列表必须为空 4.在块作用域中的lambda函数仅能捕捉父作用域中局部变量捕捉任何非此作用域或者非局部变量都会导致编译报错 5.lambda表达式之间不能相互赋值即使看起来类型相同因为是两个不同的类嘛肯定不能直接进行赋值转换 到目前为止我们基本把lambda表达式语法格式用了一遍但是好像mutable还没有提过接下来我们看看mutable的用法看下列代码 可以发现传值捕捉val后我们没有办法去修改val这是因为lambda默认是一个const函数也就是传值捕捉后的变量具有常性无法修改加上关键字mutable可取消常性  加上mutable后确实可以修改val的值了但程序打印val的值仍为20这是因为lambda是传值捕捉在lambda内部修改不会影响外部的val 所以mutable适合那种想捕捉借用父作用域的某个局部变量并对其进行修改满足自己的要求但是不希望修改后影响原父作域的这么一个场景   lambda表达式底层实现  前面提到过lambda表达式的底层实现就是类和仿函数一样用类封装函数来直接调用接下来我们用汇编源码来看看究竟是不是这么一回事 class ADD {public:int operator()(int x, int y){return x y;}};int main(){int tmp1 10;int tmp2 20;ADD fun_1;auto fun_2 [](int x, int y) {return x y; };//仿函数调用int fun_1_ret fun_1(tmp1, tmp2);//lambda调用int fun_2_ret fun_2(tmp1, tmp2);return 0;} 调试后查看这段代码的汇编代码 通过汇编代码可以看出lambda表达式的底层确实和仿函数一样都是将函数封装成一个类然后重载()只是lambda的类名对我们不可见编译器才能够识别 可变参数模板  日常使用模板都是几个模板类型C11之后推出了可变模板参数也就是模板类型的个数不需要再固定个数而是想传几个就传几个 // Args是一个模板参数包args是一个函数形参参数包// 声明一个参数包Args...args这个参数包中可以包含0到任意个模板参数。template class ...Argsvoid ShowList(Args... args){} 怎么知道是不是想传几个就传几个呢可以使用如下语句计算参数包里的参数个数 template class ...Argsvoid ShowList(Args... args){cout sizeof...(args) endl;}//计算args参数包中参数的个数 参数个数确定正确不见得参数内容就是我们传过去的所以接下来我们把参数包展开看看是不是我们传过去的参数可变参数模板参数包展开的语法比较晦涩难以理解并不需要掌握大家了解一下即可  //不可省略该函数不然无法展开void ShowList(){cout endl;}template class T, class ...Argsvoid ShowList(T val, Args... args){cout val ;ShowList(args...);}int main(){ShowList(10, 7.5, Y, string(hello));return 0;} 这个语法很晦涩难懂简单理解成递归式的调用val每次接受参数包的一个参数将其打印出来然后参数包个数减1继续向下调用直到参数包的个数为0展开结束 可变参数模板的参数包展开还有一种方法不过那个语法更加晦涩难懂笔者就不展示了 如果从事库函数的开发的话可变参数模板是个非常有用的工具举个参数模板在容器中应用的例子就以list为例去官网可以查到list在C11中多了一些成员函数 以emplace_ back()为例这个成员函数是干嘛的呢其实干得事情和push_back()是一样的就是尾插元素那有了push_back为何还要再多一个emplace_back()呢 看笔者慢慢分析  liststring test;test.push_back(string(hello workd); 上述代码就是我们把一个匿名string给push到list中整个过程就是用hello workd去构造这个匿名类然后这个匿名类会被识别为右值调用移动构造转移资源给list中的string这个过程主要有两部分的资源消耗一是构造匿名类二是调用移动构造 liststring test;test.emplace_back(hello workd); //emplace支持这种写法 使用emplace_back后就不需要去创建一个匿名类了而是可以直接把参数给传过去怎么做到的呢可以发现emplace_back的实现用到了可变参数模板 也就是说hello world会作为一个参数存放在可变参数模板包中然后编译器会用参数包中的参数去直接构造list中存放的string类不需要我们先创建一个匿名类作为载体了 相比于push_back()使用emplace_back()可以减少一次移动构造操作所以说在传右值的时候 emplace_back()会比push_back()高效那么一些。真说高效多少也不见得毕竟深拷贝下移动构造消耗资源可忽略但若不涉及深拷贝那还是不错的能省一次浅拷贝呢 包装器   有了函数指针仿函数lambda三个传递函数的法宝大家各取所需有些情况下仿函数好用些有些情况下lambda好用些但是用多了就容易乱。于是C委员会就考虑设计一种统一的接口这个接口既可以接收函数指针仿函数也可以接收lambda我们可以用这种统一的接口来调用函数指针仿函数及lambda这个接口就是包装器 看一下包装器的定义 std::function在头文件functional// 类模板原型如下 template class Ret, class... Args class functionRet(Args...);模板参数说明 Ret: 被调用函数的返回类型 Args…被调用函数的形参 包装器的本质是一个模板类把函数指针仿函数lambda等类型再进行一次包装从而达到统一调用的目的使用包装器要包含头文件functional使用时和使用模板类一样 function 返回值类型 (参数1类型参数2类型...)  类名 (函数指针/仿函数/lambda/类成员函数); 下面提供了包装器的使用示例 void fun_ptr(int x, int y){cout 函数指针: xy endl;}class FUN {public:void operator()(double x, double y){cout 仿函数: x y endl;}};int main(){//把函数指针传给包装器该函数无返回值两个int参数functionvoid(int, int) fun_1 fun_ptr;//通过包装器使用函数指针指向的函数fun_1(10, 10);//把仿函数传给包装器该函数无返回值两个double参数functionvoid(double, double) fun_2 FUN();//通过包装器使用仿函数封装的函数fun_2(20.5, 20.5);//把lambda表达式传给包装器该函数无返回值两个longlong参数functionvoid(longlong, longlong) fun_3 [](longlong x, longlong y) { cout lambda表达式: x y endl; };//通过包装器使用lambda表达式fun_3(30, 30);return 0;} 包装器看着定义比较复杂实际用起来还是挺简单的建议大家在实际开发过程中多使用包装器因为包装器能够有效的减少代码膨胀具体示例如下 void fun_ptr(int x, int y){cout 函数指针: xy endl;}class FUN {public:void operator()(int x, int y){cout 仿函数: x y endl;}};templateclass funvoid call_fun(fun f, int tmp1, int tmp2){f(tmp1, tmp2);}int main(){call_fun(fun_ptr, 10, 10);call_fun(FUN(), 10, 10);call_fun([](int x, int y){ cout lambda表达式: x y endl; }, 10, 10); return 0;} 学过模板我们仔细分析都能够明白call_fun函数会被实例化出三份因为函数指针仿函数lambda是三种不同的类型模板要为每一种类型都实例化出一份call_fun()三个函数都是一样的功能就因类型不同而被实例化出三份导致代码膨胀 使用包装器就可以解决这个问题因为函数指针仿函数lambda传给包装器后类型就变为了包装器类此时传给模板函数会识别出三个都是包装器类型只实例化一份 包装器可不仅能包装函数指针仿函数lambda表达式还能包装类内成员函数  class TEST {public:static void test_fun_static(int x, int y){cout 类内静态成员函数 x y endl;}void test_fun(int x, int y){cout 类内成员函数 x y endl;}};int main(){functionvoid(int, int) fun_1 TEST::test_fun_static;fun_1(10,20);functionvoid(int, int) fun_2 TEST::test_fun;fun_2(10,20);return 0;} 但是在编译器上你会发现无法直接用包装器封装test_fun函数而可以封装test_fun_static函数这是因为类内成员函数隐藏了一个this指针参数而类内静态成员则不含this指针 缺this指针得补上呀故而在封装类内非静态成员时还要加一个类名模板参数然后在调用时传递一个匿名类修改如下 functionvoid(TEST,int, int) fun_2 TEST::test_fun;fun_2(TEST(), 10, 20); 可能有些同学会觉得不对劲仿函数和lambda不也是类嘛为什么可以直接将仿函数和lambda传给包装器而没有this指针的问题呢这是因为你传仿函数和lambda传的是一个对象呀调用函数是通过对象中重载的()来完成传对象要什么this指针但你传类内成员函数就不一样了传的是一个函数需要考虑隐含的this指针参数 bind 接下来时此篇文章最后一部分内容bind是一个函数模板起到了适配器的作用接受一个可调用对象生成一个新的可调用对象来“适应”原对象的参数列表 什么意思呢咱们简单理解前面不是使用包装器去封装类内成员函数嘛但是类内成员有个隐藏的this指针所以不得不在包装器模板参数列表中又加了一个类名如代码 functionvoid(TEST,int, int) fun_2 TEST::test_fun; 但是TEST这么一加就会把包装器的书写格式又给破坏了调用的时候还要给一个匿名类看着就很突兀这个时候我们可以使用bind充当适配器的功能将其从新适配回funcionvoid(int, int)这种统一的格式 调用bind的一般形式auto newCallable bind(callable, arg_list);其中newCallable本身是一个可调用对象callable是要被bind的对象arg_list是一个逗号分隔的参数列表对应给定的callable的参数。当我们调用newCallable时newCallable会调用callable,并传给它arg_list中的参数 注意arg_list不是给定具体的参数类型而是用占位符placeholders::_n表示其中n是一个整数这些参数是“占位符”表示newCallable的参数它们占据了传递给newCallable的参数的“位置” 先看一个简单的绑定函数指针的例子了解绑定的用法后再解决上面问题  void fun_ptr(int x, int y){cout 函数指针 x y endl;}int main(){ //绑定fun_ptr函数auto fun_1 std::bind(fun_ptr, placeholders::_1, placeholders::_2);fun_1(10, 20);//将fun_1再赋给包装器functionvoid(int, int) fun fun_1;return 0;} 在上面的代码中因为fun_ptr有两个参数因此我们使用placeholders::_1, placeholders::_2给适配后的对象fun_1占两个参数位在调用fun_1时fun_1会调用fun_ptr并将占位符中的参数传给fun_ptr 来看看bind如何使调用类内成员函数时的格式统一示例如下 class TEST {public:static void test_fun_static(int x, int y){cout 类内静态成员函数 x y endl;}void test_fun(int x, int y){cout 类内成员函数 x y endl;}};int main(){ auto fun_1 std::bind(TEST::test_fun, TEST(), placeholders::_1, placeholders::_2);//如此便可以直接赋给包装器functionvoid(int, int) fun_2 fun_1;return 0;} 使用bind绑定类内成员时除了要给定具体的类内成员还要明确绑定的对象可以给匿名对象绑定完成后就会将其适配成统一格式可以直接赋给包装器 除此之外bind还有其他的作用以函数指针为例 void fun_ptr(int x, int y){cout 我是参数x: x endl;cout 我是参数y: y endl;}int main(){ //绑定fun_ptr函数auto fun_1 std::bind(fun_ptr, placeholders::_1, placeholders::_2);fun_1(10, 20);//如果把placeholders::_1, placeholders::_2位置对换则传过去的参数位置也会换auto fun_2 std::bind(fun_ptr, placeholders::_2, placeholders::_1);fun_2(10, 20);return 0;} 这是因为placeholders::_1就绑定到第一个参数了placeholders::_2就绑定到第二个参数上了即使你给其交换位置placeholders::_1接收到20后还是会把它传给x因为x是第一个参数还有一个玩法如下 void fun_ptr(int x, int y){cout 我是参数x: x endl;cout 我是参数y: y endl;}int main(){ //绑定fun_ptr函数且将fun_1的参数绑定具体的值auto fun_1 std::bind(fun_ptr, 100, 200);fun_1(10, 20);//绑定fun_ptr函数且将fun_2的参数绑定具体的值auto fun_2 std::bind(fun_ptr, 300, 400);fun_2(10, 20);return 0;} 至此此篇文章结束希望大家多敲一敲把这些工具都用起来
http://www.zqtcl.cn/news/302994/

相关文章:

  • 百度申诉网站建设银行住房租赁代表品牌是什么
  • 网站初期推广方案虚拟服务器搭建wordpress
  • jeecms可以做网站卖吗山西网络推广专业
  • 2017 如何做网站优化育儿哪个网站做的好
  • 网站制作容易吗青岛网站建设公司报价
  • 淘宝建设网站的好处网站制作结构
  • 网站开发网站建设公司临沂网站建设找谁
  • 咋么做网站在电脑上潍坊免费模板建站
  • 苏州网站建设推广咨询平台做网站的公司图
  • 北京企业网站怎么建设免费给我推广
  • 网站制作价钱多少专业的咨询行业网站制作
  • 做百度网站每年的费用多少交换友情链接时需要注意的事项
  • 怎么在百度网站上做自己的网站百度开户渠道
  • php技术的网站建设实录方案做二手手机的网站有哪些
  • 做网站店铺装修的软件怎么做淘课网站
  • 百度一下官方网站wordpress连接代码
  • 什么网站详情页做的好仿唧唧帝笑话门户网站源码带多条采集规则 织梦搞笑图片视频模板
  • 平原网站建设费用少儿编程加盟店倒闭
  • 企业网站建设专业公司蜜淘app在那个网站做的
  • 市住房城乡建设部网站大学生课程设计网站
  • 广州大石附近做网站的公司外包服务公司是干什么的
  • 做的新网站网上搜不到做的网站百度搜索不出来的
  • 电商网站后台报价公司如何建站
  • 查网站有没有做推广企业网站建设的目标
  • 北京网站维护公司专业外贸网站建设_诚信_青岛
  • 网站自己做还是用程序制作网站一般使用的软件有哪些
  • 晨雷文化传媒网站建设济南互联网品牌设计
  • 怎样给自己的网站做防红连接梵客装饰公司官网
  • 甘肃省城乡与住房建设厅网站纪检网站建设动态主题
  • 关于做好全国网站建设网站建设哪个好