• 【C++】C++11 包装器


    👀樊梓慕:个人主页

     🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

    🌝每一个不曾起舞的日子,都是对生命的辜负


    目录

    前言

    function包装器

    function包装器概述

    function包装器使用

    function包装器的实际应用

    bind包装器

    bind包装器概述

    bind包装器使用


    前言

    我们目前学习过的可调用对象有三种:函数指针、仿函数以及lambda表达式(实际上也是仿函数),但是这三种可调用对象却又有各自的缺点,比如函数指针类型写起来比较复杂,仿函数的类型不统一,而lambda表达式语法层上就没有类型,所以C++11引入了包装器,主要就是为了封装他们,统一类型。


    欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

    =========================================================================

    GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

    =========================================================================


    function包装器

    function包装器概述

    包装器就是对可调用对象的再封装,C++中的function本质上就是一个 『 类模板』。

    function类模板的原型如下:

    1. template <class T> function;
    2. template <class Ret, class... Args>
    3. class function<Ret(Args...)>;

    其中Ret是被包装的可调用对象的返回类型,而Args则是被包装的可调用对象的形参类型。

    function引入的目的就是为了统一三种可调用对象的类型。

    那么我们如何进行包装呢?

    function包装器使用

    1. //普通函数
    2. int f(int a, int b)
    3. {
    4. return a + b;
    5. }
    6. //仿函数对象
    7. struct Functor
    8. {
    9. public:
    10. //仿函数
    11. int operator()(int a, int b)
    12. {
    13. return a + b;
    14. }
    15. };
    16. class Plus
    17. {
    18. public:
    19. //类的静态成员函数
    20. static int plusi(int a, int b)
    21. {
    22. return a + b;
    23. }
    24. //类的非静态成员函数
    25. double plusd(double a, double b)
    26. {
    27. return a + b;
    28. }
    29. };
    30. int main()
    31. {
    32. //1、包装函数指针(函数名)
    33. function<int(int, int)> func1 = f;
    34. cout << func1(1, 2) << endl;
    35. //2、包装仿函数(函数对象)
    36. function<int(int, int)> func2 = Functor();
    37. cout << func2(1, 2) << endl;
    38. //3、包装lambda表达式
    39. function<int(int, int)> func3 = [](int a, int b){return a + b; };
    40. cout << func3(1, 2) << endl;
    41. //4、类的静态成员函数
    42. //function func4 = Plus::plusi;
    43. function<int(int, int)> func4 = &Plus::plusi; //&可省略
    44. cout << func4(1, 2) << endl;
    45. //5、类的非静态成员函数
    46. function<double(Plus, double, double)> func5 = &Plus::plusd; //&不可省略
    47. cout << func5(Plus(), 1.1, 2.2) << endl;
    48. return 0;
    49. }

    注意:

    • 包装时按照function包装器的原型套就行,返回值类型与参数类型按照定义依次替换即可;
    • 取静态成员函数的地址可以不用取地址运算符“&”,但取非静态成员函数的地址必须使用取地址运算符“&”。

    另外,因为类的非静态成员函数实际上还有一个隐藏的参数*this,所以再最后书写参数类型时,要将类的类型写在前面,并且传入匿名对象即可。


    function包装器的实际应用

    150. 逆波兰表达式求值 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/

    给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

    请你计算该表达式。返回一个表示表达式值的整数。

    注意:

    • 有效的算符为 '+''-''*' 和 '/' 。
    • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
    • 两个整数之间的除法总是 向零截断 。
    • 表达式中不含除零运算。
    • 输入是一个根据逆波兰表示法表示的算术表达式。
    • 答案及所有中间计算结果可以用 32 位 整数表示。

    解题思路:

    • 定义一个栈,依次遍历所给字符串。
    • 如果遍历到的字符串是数字则直接入栈。
    • 如果遍历到的字符串是加减乘除运算符,则从栈定抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中。
    • 所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果。

    当时,我们是这样写的:

    1. class Solution {
    2. public:
    3. int evalRPN(vector& tokens) {
    4. stack<int> s;
    5. int sum=0;
    6. for(int i=0;isize();i++)
    7. {
    8. if(!(tokens[i]=="+" || tokens[i]=="-" || tokens[i]=="*" || tokens[i]=="/"))
    9. {
    10. s.push(atoi(tokens[i].c_str()));
    11. }
    12. else
    13. {
    14. int right=s.top();
    15. s.pop();
    16. int left=s.top();
    17. s.pop();
    18. switch(tokens[i][0])
    19. {
    20. case '+':
    21. s.push(left+right);
    22. break;
    23. case '-':
    24. s.push(left-right);
    25. break;
    26. case '*':
    27. s.push(left*right);
    28. break;
    29. case '/':
    30. s.push(left/right);
    31. break;
    32. }
    33. }
    34. }
    35. return s.top();
    36. }
    37. };

     那现在我们学习了很多C++的新容器新内容,我们可以写出一份更加漂亮的代码出来。

    我们可以利用map容器和function包装器对以上代码做优化。

    map容器存储的是键值对,那么我们就可以将操作符字符串作为键,将具体要执行的操作作为值,那这样我们利用map的[]操作符就可以得到当前要进行什么操作了。

    我们可以采用很多种方式将可调用对象赋给map的值,这里我们使用lambda表达式,你当然也可以使用函数指针、仿函数等,但明显这里使用lambda表达式更加方便。

    但是lambda表达式没有类型,所以我们就可以使用包装器将lambda表达式包装成function包装器,统一了类型。

    1. class Solution {
    2. public:
    3. int evalRPN(vector& tokens) {
    4. stack<int> st;
    5. unordered_mapint(int, int)>> opMap = {
    6. { "+", [](int a, int b){return a + b; } },
    7. { "-", [](int a, int b){return a - b; } },
    8. { "*", [](int a, int b){return a * b; } },
    9. { "/", [](int a, int b){return a / b; } }
    10. };
    11. for (const auto& str : tokens)
    12. {
    13. int left, right;
    14. if (str == "+" || str == "-" || str == "*" || str == "/")
    15. {
    16. right = st.top();
    17. st.pop();
    18. left = st.top();
    19. st.pop();
    20. st.push(opMap[str](left, right));
    21. }
    22. else
    23. {
    24. st.push(stoi(str));
    25. }
    26. }
    27. return st.top();
    28. }
    29. };

    从这里,我们就可以看出来包装器function的意义:

    • 将可调用对象的类型进行统一,便于我们对其进行管理。
    • 包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用。

    bind包装器

    bind包装器概述

    bind包装器有以下两种作用:

    • 调整参数的顺序(价值不大);
    • 调整参数的个数;

    bind函数模板的原型如下:

    1. template <class Fn, class... Args>
    2. /* unspecified */ bind(Fn&& fn, Args&&... args);
    3. template <class Ret, class Fn, class... Args>
    4. /* unspecified */ bind(Fn&& fn, Args&&... args);

     其中fn是可调用对象,args...是要绑定的参数列表。


    bind包装器使用

    调用bind的一般形式为:auto newCallable = bind(callable, arg_list);

    其中:

    • newCallable 为 新的可调用对象;
    • callable 为 被包装的可调用对象;
    • arg_list 为 被绑定的参数列表,一般为固定写法,比如placeholders::_1代表第一个参数,placeholders::_2代表第二个参数。

    那我们上面说的调整参数的顺序就可以通过修改这个参数列表中的数据来实现了。

    比如:

    1. int Sub(int a, int b)
    2. {
    3. return a - b;
    4. }
    5. int main()
    6. {
    7. //调整参数顺序
    8. auto f1 = bind(Sub, placeholders::_2, placeholders::_1);
    9. cout << f1(x, y) << endl;
    10. return 0;
    11. }

    因为『 placeholders::_1』代表的就是第一个参数的位置,所以他与『 placeholders::_2』位置进行交换,就意味着封装后的f1对象,参数位置发生了改变。

    那调整参数个数呢?

    比如,由于非静态成员函数参数有一个隐藏的this指针,那么我们不想每次都要把它传进去,我们就可以利用bind包装一个新的函数对象出来:

    1. class Plus
    2. {
    3. public:
    4. static int plusi(int a, int b)
    5. {
    6. return a + b;
    7. }
    8. double plusd(double a, double b)
    9. {
    10. return a - b;
    11. }
    12. };
    13. int main()
    14. {
    15. //调整参数顺序
    16. //某些参数绑死
    17. function<double(double, double)> fc4 = bind(
    18. &Plus::plusd
    19. ,Plus()
    20. ,placeholders::_1
    21. ,placeholders::_2
    22. );
    23. cout << fc4(2, 3) << endl;
    24. //这样我们使用fc4时就传递两个参数就可以了,就不需要再每次将隐藏的this也传递了
    25. function<double(double)> fc5 = bind(
    26. &Plus::plusd
    27. ,Plus()
    28. ,placeholders::_1
    29. ,20
    30. );
    31. cout << fc5(2) << endl;
    32. //fc5绑死了两个参数
    33. return 0;
    34. }

    以上这种绑死某个参数的做法可以用于比如这个参数很固定是某个值,我们就可以采用这种方法。

    那么bind包装器的意义我们总结如下:

    • 当一个函数的某些参数为固定的值时,我们可以使用bind包装器绑死某个参数。
    • bind包装器可以对函数参数的顺序进行灵活调整。

    =========================================================================

    如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

    🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

    🌟~ 点赞收藏+关注 ~🌟

    =========================================================================

  • 相关阅读:
    获取用户真实 ip
    java学习day21(File类和IO流)缓冲流、转换流、序列化流、打印流
    自然语言处理(NLP)—— 主题建模
    spring boot中shiro使用自定义注解屏蔽接口鉴权
    kubernetesr进阶--污点和容忍之基于污点的驱逐
    软件自动化测试作用简析,为什么要选择第三方软件测评机构?
    【马士兵】 Python基础--11
    leetcode 49. 字母异位词分组
    测试左移:传统测试方式该如何过渡
    btstack协议栈实战篇--Performance - Stream Data over SPP (Server)
  • 原文地址:https://blog.csdn.net/2301_77112634/article/details/137237328