lambda表达式、function、bind)
hello~ 很高兴见到大家! 这次带来的是C中关于C11这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?个 人 主 页: 默|笙文章目录一、lambda1. lambda表达式语法2. 捕捉列表和mutable关键字3. lamba的原理4. 应用二、包装器1. function2. bind接上次的博客C11二可变参数模板、emplace系列接口、默认的移动构造和移动赋值一、lambda1. lambda表达式语法lambda表达式本质是一个匿名函数对象这个原理部分会讲到不过与普通函数只能定义在全局或类内部不同它可以直接定义在函数内部。lambda表达式格式[capture_list](parameters)-returntype{function boby};[capture_list]捕捉列表存在于lambda函数起始位置它的存在能让编译器识别出这是一个lambda函数。作用捕捉列表能够捕捉上下文里的局部变量和参数供lambda函数使用全局变量、静态变量可以直接使用。即使什么都不捕捉捕捉列表也不能省略。因为编译器要通过它来识别这个lambda函数。(parameters)参数列表和普通函数一样参数列表功能一样不过如果不用传参且不显式指定返回值类型的话()是能够省略的但如果使用了之后会讲到的mutable修饰符参数列表便不可省略。- return type返回值类型这里是返回值类型的尾置语法也就是把返回值类型放在了参数列表的后面。如果没有返回值的话这里可以省略。也可以不写纯靠编译器推理出这里的返回值类型。{ function boby }函数体这里和普通函数没有什么区别。可以在里面使用捕捉列表捕捉到的变量、传递来的形参、在内部定义的变量以及全局变量、静态变量。函数体为空也不能省略。lambda表达式在编译时会被转换为一个匿名类这个类的实例就是lambda函数对象。我们通过这个匿名函数对象来调用lambda。lambda表达式对于语法使用层面没有类型我们一般是通过auto或者是模板参数定义的对象都可以依靠编译器去推理去接收lambda对象。autoadd[](intx,inty)-int{returnxy;};intnadd(1,2);coutnendl;[](int x, int y)-int { return x y; };是一个lambda表达式也就是一个匿名函数对象我通过变量add来接收这个对象。之后通过add来调用这个lambda函数。2. 捕捉列表和mutable关键字lambda表达式默认是只能使用lambda函数体和参数列表中的变量以及全局变量静态变量。如果想要用外层作用域中的变量就要进行捕捉。第一种捕捉方式是显式在捕捉列表里传值捕捉和传引用捕捉。捕捉的多个变量之间需要用逗号进行分割比如[ x, y, z]其中x和y是传值捕捉z是传引用捕捉。第二种捕捉方式是隐式捕捉它也包含传值捕捉和传引用捕捉捕捉列表里是就是传值捕捉是就是传引用捕捉。它们会根据函数体内使用了外层作用域的哪些变量来进行自动捕捉也就是编译器自动捕捉所有使用到的变量。[]与[]第三种捕捉方式是混合捕捉也就是显式捕捉和隐式捕捉结合在一起使用。不过隐式捕捉或者一定要是第一个元素且能和混合使用的一定是显式引用捕捉和混合使用的一定是显式传值捕捉。比如[, x, y][, x, y]。lambda表达式如果在函数局部域中他可以捕捉lambda表达式之前所定义的变量但不能捕捉全局变量和静态变量这两个不用捕捉就能直接使用。这同时也意味着如果lambda表达式如果定义在全局捕捉列表必须为空。传值捕捉不能通过修改这个捕捉来的变量来影响外层作用域的变量因为是副本但传引用捕捉是直接修改外层作用域变量的除非原变量不可修改。由于lambda捕捉列表是被const修饰的也就是说传值捕捉过来的变量是不能进行修改的如果要修改则需要在捕捉列表后面加上修饰符mutable这样可以取消它的常量性但由与传值捕捉的是副本所以就算可以修改这个捕捉过来的变量也是不能够影响到外层作用域的变量的实参。使用mutable修饰符后参数列表即使为空也不能够省略。inta1,b2;autoadd[a,b]()mutable{a2;b5;};add();couta bendl;可以看到a即使被mutable解除常性也无法影响实参而b引用捕捉却可以。3. lamba的原理lamba表达式的底层是一个仿函数类。这个仿函数的类名是编译器根据特定规则生成的保证不同lambda生成的类名不同。lambda表达式捕捉列表去捕捉变量本质就是生成这个lambda类的成员变量也就是说捕捉列表里的变量会作为这个匿名类的成员变量并在创建lambda对象时通过构造函数进行初始化。对于传值捕捉这些成员变量是外部变量的副本对于传引用捕捉这些成员变量是引用。这也能完全解释为何传值无法影响到外层作用域的变量因为操作的是副本而后面的参数列表返回值和函数体则对应lambda类中operator()的重载。autoadd[a,b]()mutable{a2;b5;};class类名//类名由编译器生成{public:类名(inta,intb):_a(a),_b(b){}voidoperator()()//如果没有mutable后面加上const{_a2;_b5;}private:int_a;int_b;};4. 应用在学习lambda表达式之前我们能够使用的可调用对象只有函数指针和仿函数对象函数指针的定义和定义仿函数定义一个类都比较麻烦。而有了lambda表达式之后功能和仿函数一样用它去定义简单又方便。二、包装器1. functionstd::function是一个类模板也是一个包装器。它的实例对象可以包装其他的可调用对象比如lambda表达式函数指针、仿函数、bind表达式等。存储的可调用对象称作std::function的目标。如果std::function不含目标则称它为空空的std::function在使用时会抛异常。函数指针、仿函数、lambda表达式、bind表达式等可调用对象的类型各不相同而function则可以统一它们的类型类型擦除变成std::function类型。这样方便之后统一的存储和调用比如存进容器里面。使用function可调用对象返回类型(可调用对象参数类型) 对象名称 可调用对象。对于类成员函数c规定要指定类域并取地址。使用function和之后会讲到的bind需要包含头文件functional。#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;};//包装静态成员函数成员函数需要指定类域且前面加才能获取地址functionint(int,int)f4Plus::plusi;//不过静态成员函数前面可以不加但是推荐加functionint(int,int)f5Plus::plusi;//包装普通类成员函数不要忘了成员函数的第一个参数是this指针。Plus obj;//传递对象指针-最常用的方式functiondouble(Plus*,double,double)f6Plus::plusd;doubler1f6(obj,1.1,2.2);//传递地址//传递对象--有拷贝functiondouble(Plus,double,double)f7Plus::plusd;doubler2f7(obj,1.1,2.2);//传递右值引用functiondouble(Plus,double,double)f8Plus::plusd;doubler3f8(move(obj),1.1,2.2);//传递左值引用functiondouble(Plus,double,double)f9Plus::plusd;doubler4f9(obj,1.1,2.2);return0;}function包装类普通成员函数的第一个参数不是this指针类型而是调用成员函数所需的一个媒介的类型无论是对象指针还是对象对象引用都是一个调用成员函数的媒介编译器通过这些媒介来获取实际的this指针。如果要包装类普通成员函数要将第一个参数加上const那么对应的函数也要加上const进行修饰权限不能放大。2. bindbind是一个函数模板同时它也是一个可调用对象的包装器。它会将接收的可调用对象进行一个再加工处理然后返回一个可调用对象比如用来调整参数个数和参数顺序。bind也在finctional这个头文件中。调用bind的一般形式是auto newCallable bind(callable, arg_list);其中newCallable是一个加工完成之后的可调用对象arg_list是一个逗号分隔的参数列表callable是要加工的可调用对象这个newCallable的arg_list参数列表对应的就是callable的参数列表。当我们调用newCallable时newCallable会调用callable然后将自己的参数列表arg_list传递给callable的参数列表。arg_list中的参数一般包含形如_n的名字其中n是一个整数这些参数是占位符表示newCallable的参数。数值n表示的是生成的可调用对象中参数的位置。_1就是第一个需要传递给newCallable的参数_2是第二个以此类推。改变顺序是改变像_1_2这样的参数在参数列表中的位置而调整参数个数是通过给定值来达到使用newCallable时就不用传递定值所在位置的实参newCallable在传递参数给callable时会把这个定值当作实参之一。_1/_2…这些占位符在一个叫placeholders的一个命名空间内。#includefunctionalusingplaceholders::_1;usingplaceholders::_2;usingplaceholders::_3;intSub(inta,intb){returna-b;}intmain(){coutSub(5,10)endl;autonewSub1bind(Sub,_2,_1);coutnewSub1(5,10)endl;autonewSub2bind(Sub,5,_1);coutnewSub2(10)endl;return0;}指得注意的是减少参数里虽然传的10是第二个参数但是占位符依然得从_1开始哦。bind(类名::成员函数,对象指针/引用,绑定参数/占位符...);如果绑定的是类的成员函数那么第二个位置必须传成员对象指针。如果绑定的是全局函数或者是静态成员函数那么第二个位置可以省略它们不依赖对象指针。非静态成员函数自带隐式this参数第二个位置必须写对象不能省略对象可以是 obj指针后面写固定参数或者占位符_1,_2调用时再传参。今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~让我们共同努力, 一起走下去!