C++11新增特性:lambda表达式、function包装器、bind绑定

打印 上一主题 下一主题

主题 876|帖子 876|积分 2628

一、lambda表达式

1)、为啥需要引入lambda?

 在c++98中,我们使用sort对一段自定义类型进行排序的时候,每次都需要传一个仿函数,即手写一个完备的类。甚至偶然需要同时实现排升序和降序,就需要各自手写一个类,非常不方便!所以C++11引入了lambda表达式!
lamaba是一个匿名函数对象,是一个可调用对象,表达式本质上也是一个类(vs中类名为lambda_uuid),并实现了operator()。
【C98玩法】:
  1. struct Goods
  2. {
  3.         string _name;  // 名字
  4.         double _price; // 价格
  5.         int _evaluate; // 评价
  6.         Goods(const char* str, double price, int evaluate)
  7.                 :_name(str)
  8.                 , _price(price)
  9.                 , _evaluate(evaluate)
  10.         {}
  11. };
  12. struct ComparePriceLess
  13. {
  14.         bool operator()(const Goods& gl, const Goods& gr)
  15.         {
  16.                 return gl._price < gr._price;
  17.         }
  18. };
  19. struct ComparePriceGreater
  20. {
  21.         bool operator()(const Goods& gl, const Goods& gr)
  22.         {
  23.                 return gl._price > gr._price;
  24.         }
  25. };
  26. int main()
  27. {
  28.         vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 },
  29.                                                 { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
  30.         sort(v.begin(), v.end(), ComparePriceLess());
  31.         sort(v.begin(), v.end(), ComparePriceGreater());
  32. }
复制代码
【C++11 lambda表达式】:
  1. int main()
  2. {
  3.         vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 },
  4.                                                 { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };
  5.         sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
  6.                 return g1._price < g2._price; });
  7.         sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
  8.                 return g1._price > g2._price; });
  9.         sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
  10.                 return g1._evaluate < g2._evaluate; });
  11.         sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
  12.                 return g1._evaluate > g2._evaluate; });
  13. }
复制代码
2)、lambda表达式语法

lambda表达式誊写格式:[capture-list] (parameters) mutable -> return-type { statement }


  • capture-list(捕捉列表):编译器根据[ ]来判断接下来的代码是否为lambda函数,[ ]可以捕捉父作用域的变量供lambda函数使用。
  • parameters(列表参数):和普通函数列表参数一样。假如没用,可以连同()一起省略。
  • mutable 默认情况下lambda是一个const函数(现实上是用于修饰operator()的),mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • return-type:用于声明返回值类型。假如没用返回值可以省略;即使有返回值也可以省略,由编译器自动推导!
  • statement 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
在lambda函数定义中,参数列表和返回值类型都是可选部门,而捕捉列表和函数体可以为空。因此C++11中最简朴的lambda函数为:[]{}; 该lambda函数不能做任何变乱。
3)、capture-list(捕捉列表)细节

 捕捉列表用于捕捉父作用域中变量供lambda函数使用,捕捉方分为以下几种:

  • [var]:表现值传递方式捕捉变量var。
  • [=]:表现传值方式捕捉父作用域中的所有变量(包括this)。
  • [&var]:表现引用传递方式捕捉变量var。
  • [&]:表现引用方式捕捉父作用域中的所有变量(包括this)。
  • [this]:表现值传递方式捕捉当前的this指针
 除此之外,捕捉列表可以由多个捕捉项组成,以逗号分割。比如:
     [=, &a, &b]:除引用捕捉a、b外,其他父作用域变量传值捕捉!
     [&, a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
 并且捕捉列表不能重复变量传递,否则会导致编译错误。比如:[=, a]:a变量传递了两次。lambda表达式之间不能相互赋值,即使类型相同,但编译后类名都变为了lambda_uuid),类名不相同!
4)、函数对象与lambda表达式

 函数对象又称仿函数,既可以像普通函数一样使用(在类中重载operator())。而lambda本质上一个一个函数对象,在编译后会转化成一个仿函数,并对operator()进行重载!
  1. class Rate
  2. {
  3. public:
  4.         Rate(double rate) : _rate(rate)
  5.         {}
  6.         double operator()(double money, int year)
  7.         {
  8.                 return money * _rate * year;
  9.         }
  10. private:
  11.         double _rate;
  12. };
  13. int main()
  14. {
  15.         // 函数对象
  16.         double rate = 0.49;
  17.         Rate r1(rate);
  18.         r1(10000, 2);
  19.         // lamber
  20.         auto r2 = [=](double monty, int year)->double {
  21.                 return monty * rate * year;
  22.         };
  23.         r2(10000, 2);
  24.         return 0;
  25. }
复制代码
【反汇编结果】:

二、function包装器

1)、包装器由来

 function包装器 也叫作适配器,包装的是可调用对象。C++中的function本质是一个类模板,也是一个包装器。
在C++中有3种可调用对象:函数指针、仿函数、lambda!但我们发现想要获得他们的都存在一些缺陷:函数指针非常麻烦,类型和对象嵌在一起;仿函数很重,即使是一个很简朴的比力逻辑也要在外面定义一个类;lambda没法写类型,差别的呆板和时间uuid差别,并且相对匿名(固然decltype可以获的)
除此在外,众多的可调用对象类型会导致模板的效率低下! 缘故原由在于假设现在存在一个函数模板,我需要将一个可调用对象作为模板参数进行传递来达到某种目的。但众多的可调用对象可能实现的是同一个功能,但分别传递给模板后需要实例化3份,导致模板效率低落!!所以包装器对可调用对象进行了统一!
【实例】:
  1. template<class F, class T>
  2. T useF(F f, T x)
  3. {
  4.         static int count = 0;
  5.         cout << "count:" << ++count << endl;
  6.         cout << "count:" << &count << endl;
  7.         return f(x);
  8. }
  9. double f(double i)
  10. {
  11.         return i / 2;
  12. }
  13. struct Functor
  14. {
  15.         double operator()(double d)
  16.         {
  17.                 return d / 3;
  18.         }
  19. };
  20. int main()
  21. {
  22.         // 函数名
  23.         cout << useF(f, 11.11) << endl;
  24.         // 函数对象
  25.         cout << useF(Functor(), 11.11) << endl;
  26.         // lamber表达式
  27.         cout << useF([](double d){ return d / 4; }, 11.11) << endl;
  28.         return 0;
  29. }
复制代码
2)、包装器原型

  1. std::function在头文件<functional>
  2. // 类模板原型如下
  3. template <class T> function;     // undefined
  4. template <class Ret, class... Args>
  5. class function<Ret(Args...)>;
  6. 模板参数说明:
  7. Ret: 被调用函数的返回类型
  8. Args…:被调用函数的形参
复制代码
即现在存在一个包装器为:function<int(int, int)>,该包装器将所有的参数为2个int,返回值为int的所有可调用对象进行统一,认为是一个类型!!
3)、使用

  1. template<class F, class T>
  2. T useF(F f, T x)
  3. {
  4.         static int count = 0;
  5.         cout << "count:" << ++count << endl;
  6.         cout << "count:" << &count << endl;
  7.         return f(x);
  8. }
  9. double f(double i)
  10. {
  11.         return i / 2;
  12. }
  13. struct Functor
  14. {
  15.         double operator()(double d)
  16.         {
  17.                 return d / 3;
  18.         }
  19. };
  20. int main()
  21. {
  22.         // 函数名
  23.         std::function<double(double)> func1 = f;
  24.         cout << useF(func1, 11.11) << endl;
  25.         // 函数对象
  26.         std::function<double(double)> func2 = Functor();
  27.         cout << useF(func2, 11.11) << endl;
  28.         // lamber表达式
  29.         std::function<double(double)> func3 = [](double d){
  30.                 return d;
  31.          };
  32.         cout << useF(func3, 11.11) << endl;
  33.         return 0;
  34. }
复制代码
4)、口试题

150. 逆波兰表达式求值

  1. class Solution {
  2. public:
  3.     int evalRPN(vector<string>& tokens) {
  4.         unordered_map<string, function<int(int, int)>> ophash = {
  5.             {"+", [](int x, int y){return x + y;}},
  6.             {"-", [](int x, int y){return x - y;}},
  7.             {"*", [](int x, int y){return x * y;}},
  8.             {"/", [](int x, int y){return x / y;}}
  9.         };
  10.         stack<int> st;
  11.         for(auto str : tokens)
  12.         {
  13.             if(ophash .find(str) != ophash .end())
  14.             {
  15.                 int right = st.top(); st.pop();
  16.                 int left = st.top(); st.pop();
  17.                 st.push(ophash [str](left, right));
  18.             }
  19.             else
  20.                 st.push(stoi(str));
  21.         }
  22.         return st.top();
  23.     }
  24. };
复制代码
三、bind绑定

bind也是一个函数模板,可以吸收一个可调用对象,并返回一个新的可调用对象,用于调整参数的个数温顺序!!
  1. // 原型如下:
  2. template <class Fn, class... Args>
  3. /* unspecified */ bind (Fn&& fn, Args&&... args);
  4. // with return type (2)
  5. template <class Ret, class Fn, class... Args>
  6. /* unspecified */ bind (Fn&& fn, Args&&... args);
复制代码
 调用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)普通绑定用法

【实例】:
  1. int Sub(int a, int b)
  2. {
  3.         return a - b;
  4. }
  5. int main()
  6. {
  7.         std::function<int(int, int)> func1 = std::bind(Plus,
  8.                                                                                  std::placeholders::_1, std::placeholders::_2);
  9.         std::cout << func1(10, 2) << std::endl;
  10. }
复制代码

    bind函数中,_1和_2都是占位符。当实行func1(10, 2)函数时,参数10和2会被作为参数传递给Plus函数。其中std::placeholders::_1表现func1第一个参数的位置, std::placeholders::_2表现第二个参数地点位置!
  2)、改变参数位置

 既然_1和_2是占位符,所以仅需改变两者位置,所以func函数传递给Plus的参数即可转换。具体结果如下:
  1. int Sub(int a, int b)
  2. {
  3.         return a - b;
  4. }
  5. int main()
  6. {
  7.         std::function<int(int, int)> func1 = std::bind(Sub,
  8.                                                                                  std::placeholders::_2, std::placeholders::_1);
  9.         std::cout << func1(10, 2) << std::endl;
  10. }
复制代码

3)改变参数的个数

 我们在类外调用一个类成员函数,调用成员函数时第一个参数为该对象的指针。但每次我们手动传递的时候非常麻烦。所以我们可以通过bind()将第一个参数进行绑死,改变参数个数。具体如下:
  1. class Plus
  2. {
  3. public:
  4.         static int Plusi(int x, int y)
  5.         {
  6.                 return x + y;
  7.         }
  8.         double Plusd(double x, double y)
  9.         {
  10.                 return x + y;
  11.         }
  12. };
  13. int main()
  14. {
  15.         Plus ps;
  16.        
  17.         // 正常情况下,第一个参数this指针需要手动传递, 麻烦
  18.         std::function<double(Plus*, double, double)> func1 = &Plus::Plusd;
  19.         std::cout << func1(&ps, 10.1, 20.2) << std::endl;
  20.         // 将第一个参数绑死为Plus*,
  21.         std::function<double(double, double)> func2 = std::bind(&Plus::Plusd,
  22.                                                                                  &ps, std::placeholders::_1, std::placeholders::_2);
  23.         std::cout << func2(10.1, 20.2) << std::endl;
  24.         // 将第一个参数和第三个参数绑死
  25.         std::function<double(double)> func3 = std::bind(&Plus::Plusd,
  26.                                                                                                         &ps, std::placeholders::_1, 1.1);
  27.         std::cout << func3(10.1) << std::endl;
  28.        
  29.         // 所有参数直接全部绑死, 注意此处博主绑定的是静态函数
  30.         auto func4 = std::bind(&Plus::Plusi, 10, 10);
  31.         std::cout << func4() << std::endl;
  32.        
  33.         //绑定时,第一个参数因为指针,但编译器做了特殊处理,可以绑定临时对象
  34.         std::function<double(double, double)> func5 = std::bind(&Plus::Plusd,
  35.                                                                         Plus(), std::placeholders::_1, std::placeholders::_2);
  36.         std::cout << func5(1.1, 2.2) << std::endl;
  37. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

汕尾海湾

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表