C++11 包装器(上)

简介: C++11 包装器

function包装器

function包装器介绍

function是一种函数包装器 也叫做适配器 它可以对可调用对象进行包装 C++中的function本质就是一个类模板


它的类模板原型如下

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;


模板参数说明:

  • Ret:被包装的可调用对象的返回值类型
  • Args…:被包装的可调用对象的形参类型


下面我们会使用包装器对可调用对象进行包装

首先我们写出下面的代码做好前置准备

int f(int a, int b)
{
  return a + b;
}
struct Functor
{
public:
  int operator()(int a, int b)
  {
    return a + b;
  }
};
class Plus
{
public:
  static int plusi(int a, int b)
  {
    return a + b;
  }
  double plusd(double a, double b)
  {
    return a + b;
  }
};


包装函数指针(函数名)

// 包装函数指针(函数名)
  function<int(int, int)> f1 = f;
  cout << f1(3, 3) << endl;

dfb75dd28d98456cb2c924169192ff4a.png

包装仿函数(函数对象)

// 包装仿函数(函数对象)
  function<int(int, int)> f2 = Functor();
  cout << f2(3, 3) << endl;

e5c1b28b68e545b5a4ad47ebc453a28c.png

包装lambda表达式

// 包装lambda表达式
  function<int(int, int)> f3 = [](int x, int y)mutable->int {return x + y; };
  cout << f3(3, 4);

ca319bf9c21e4f7790eb204d931632cc.png

类的静态成员变量

// 类的静态成员函数
  function<int(int, int)> f4 = &Plus::plusi; // 对于静态成员变量来说 &符号可以省略
  cout << f4(3, 4);

129372ba023f4bab83667f2216f99be7.png

类的非静态成员变量

// 类的非静态成员变量 
  function<int(Plus, int, int)> f5 = &Plus::plusd;
  cout << f5(Plus(), 2, 1);

b243246eeabf4030bd28fa135165e484.png


注意事项:

  • 包装时要指明返回值还有各种参数类型 然后将可调用对象赋值给function包装器即可 包装后的的function对象就可以像普通函数一样调用了
  • 对于静态成员函数 & 可加可不加 对于非静态成员函数 &必须要加
  • 包装非静态成员函数时候第一个参数是隐藏的this指针 所以说在包装时要指明第一个是类类型的参数


function包装器统一类型

对于以下函数模板useF:

template<class F, class T>
T useF(F f, T x)
{
  static int count = 0;
  cout << "count: " << ++count << endl;
  cout << "count: " << &count << endl;
  return f(x);
}
  • 传入该模板的第一个参数可以是任意类型的可调用对象 比如说匿名函数 函数指针 函数对象等
  • 我们在该模板的内部设置了一个静态成员变量count 并且使用两个count语句打印出count的大小和位置 这两个语句的意义是判断是否公用一个实例

我们保持第二个参数相同 试着传入不同的第一个参数

  // 函数指针
  useF(f, 3);
  // 函数对象/仿函数
  useF(Functor(), 3);
  // 匿名函数/lambda表达式
  useF([](int x) {return x / 2; }, 3);


这里说明下

  • 在参数中写lambda表达式时 最后的分号不写 用逗号代替标识lambda表达式的结束

最后的运行结果是这样子的6ca54e5ef7844db491962d445a59a571.png

通过观察我们可以发现两点

  • count的地址都不相同 所以说函数模板实例化出了三个不同的对象
  • count的大小都是1 说明了三个实例化的函数都只调用了一次


一个模板 为了完成一个相同的任务竟然被实例化出了三份 这是追求高效的C++所不能忍受的 所以我们可以使用C++11中的包装器来解决这个问题

  function<double(double)> f1 = f; // 函数
  function<double(double)> f2 = Functor(); // 函数
  function<double(double)> f3 = [](double x)mutable->double {return x / 2; }; // lamada表达式
  useF(f1, 3);
  useF(f2, 3);
  useF(f3, 3);

我们分别对于三个类型的函数对象进行包装之后再使用usef调用之

6a43111fe35244c3b54ca2f623e4bd2a.png

结果上

  • 我们可以发现三次打印出来的count都是同一地址
  • count的大小为3 说明这个实例化的函数被条用了三次

原理上

  • 造成这样现象的本质是因为经过function的包装之后 它们的类型都变成了function类型

以下是证明代码和截图

  function<double(double)> f1 = f; // 函数
  function<double(double)> f2 = Functor(); // 函数
  function<double(double)> f3 = [](double x)mutable->double {return x / 2; }; // lamada表达式
  cout << typeid(f1).name() << endl;
  cout << typeid(f2).name() << endl;
  cout << typeid(f3).name() << endl;

2853dfee9d9747f09a7a811f76369c6c.png


function简化代码

我们再求解逆波兰表达式这道题目的时候是这么操作的

  1. 定义一个栈 并且遍历整个字符串
  2. 如果遍历到的是字符则直接入栈
  3. 如果遍历到的是运算符则从栈中取出两个数字运算 运算完毕后入栈
  4. 重复上面的步骤直到字符串遍历完毕 最后栈顶元素就是结果


我之前写的leetcode代码如下

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for (auto x : tokens)
        {
            if (x == "+" || x =="-" || x =="*" || x =="/")
            {
                long num1 = st.top(); st.pop();
                long num2 = st.top(); st.pop();
                if (x == "+") st.push(num2 + num1);
                if (x == "-") st.push(num2 - num1);
                if (x == "*") st.push(num2 * num1);
                if (x == "/") st.push(num2 / num1);
            }
            else
            {
                st.push(stol(x));
            }
        }
        return st.top();
    }
};

在上述代码中 我们使用了四个if语句来判断运算符是哪种类型

在学习了function包装器之后我们可以换一种思路

使用map容器来绑定字符串和function对象 使每个运算符对应一个function对象

每次遍历的字符串都在map容器中查找 如果找到就是运算符开始运算 如果找不到就不是运算符 直接入栈


改进后的代码如下

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string,function<int(int,int)>> solmap;
        solmap["+"] = [](int x,int y){return x + y;};
        solmap["-"] = [](int x,int y){return x - y;};
        solmap["*"] = [](int x,int y){return x * y;};
        solmap["/"] = [](int x,int y){return x / y;};
        for (auto x : tokens)
        {
            auto it = solmap.find(x);
            if (it == solmap.end())
            {
                st.push(stoi(x));
            }
            else
            {
                long right = st.top();
                st.pop();
                long left = st.top();
                st.pop();
                auto e = solmap[x](left,right);
                st.push(e);
            }
        }
        return st.top();
    }
};

看起来代码好像没有精简多少 这是因为运算符的个数太少了 当运算符的数目很多时 这种方式对于代码精简的作用才会显示出来


function包装器的意义

  • 将可调用对象的类型进行统一 便于我们对其进行统一化管理
  • 包装后明确了可调用对象的返回值和形参类型 更加方便使用者使用
相关文章
|
6天前
|
C++
【C++】bind绑定包装器全解(代码演示,例题演示)
【C++】bind绑定包装器全解(代码演示,例题演示)
|
7月前
|
编译器 C++
C++11:包装器
C++11:包装器
54 1
|
6天前
|
存储 算法 C++
C++11:lambda表达式 & 包装器
C++11:lambda表达式 & 包装器
11 0
|
6天前
|
存储 算法 对象存储
【C++入门到精通】function包装器 | bind() 函数 C++11 [ C++入门 ]
【C++入门到精通】function包装器 | bind() 函数 C++11 [ C++入门 ]
17 1
|
6天前
|
Java 编译器 Linux
【C++11(二)】lambda表达式以及function包装器
【C++11(二)】lambda表达式以及function包装器
|
6天前
|
存储 算法 C++
【C++ 包装器类 std::tuple】全面入门指南:深入理解并掌握C++ 元组 std::tuple 的实用技巧与应用(三)
【C++ 包装器类 std::tuple】全面入门指南:深入理解并掌握C++ 元组 std::tuple 的实用技巧与应用
33 0
|
6天前
|
存储 编解码 数据库
【C++ 包装器类 std::tuple】全面入门指南:深入理解并掌握C++ 元组 std::tuple 的实用技巧与应用(二)
【C++ 包装器类 std::tuple】全面入门指南:深入理解并掌握C++ 元组 std::tuple 的实用技巧与应用
56 0
|
6天前
|
存储 编译器 数据库
【C++ 包装器类 std::tuple】全面入门指南:深入理解并掌握C++ 元组 std::tuple 的实用技巧与应用(一)
【C++ 包装器类 std::tuple】全面入门指南:深入理解并掌握C++ 元组 std::tuple 的实用技巧与应用
54 0
|
6天前
|
存储 安全 算法
【C++ 包装器类 std::atomic 】全面入门指南:深入理解并掌握C++ std::atomic 原子操作 的实用技巧与应用
【C++ 包装器类 std::atomic 】全面入门指南:深入理解并掌握C++ std::atomic 原子操作 的实用技巧与应用
77 1
|
6天前
|
存储 安全 C++
【C++ 包装器类 std::optional】全面入门指南:深入理解并掌握C++ std::optional的实用技巧与应用
【C++ 包装器类 std::optional】全面入门指南:深入理解并掌握C++ std::optional的实用技巧与应用
63 0