包装器

时间:2024-04-11 16:23:23

在寄快递的时候,快递会进行一次包装,这样我们就可以统一的在上面贴上快递信息,随后以统一的形式管理所有快递。包装器也是如此,包装器可以将具有相似属性的东西包装起来成为一个整体。

function

如果一个变量f,可以按照f()的形式调用函数,那么称f是一个可调用对象

回顾一下,现在我们有那些可调用对象

  1. 函数指针,函数名(函数名的本质就是函数指针)
  2. 仿函数实例化出的对象
  3. lambda表达式

这三者,都可以直接加一对()进行函数调用。它们都有各自的缺点:

  • 函数指针,函数名:类型复杂,不好用
  • 仿函数实例化出的对象:哪怕参数返回值都相同,仿函数之间的类型也不同
  • lambda表达式:类型是随机的,必须用auto接收

可以看到,这三者都有类型方面的大问题,我们也没有一种方式可以把所有参数类型和返回值类型相同的函数,统一的管理起来,让它们都变成一个类型?

包装器function就可以做到该工作,function被包含在头文件<functional>中,是一个类模板,模板原型如下:

template <class T> function;

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

其语法为:function<返回值(参数列表)>只要所有返回值和参数列表相同的可调用对象,经过这一层封装,都会变成相同的类型

比如我们现在有如下三个函数:

double func(double x)
{
    return x / 2;
}

struct Functor
{
    double operator()(double x)
    {
        return x / 3;
    }
};

int main()
{
    auto lambadaFunc = [](double d) {return d / 4; };

    return 0;
}

分别是func函数,Functor仿函数,以及lambda表达式lambadaFunc 。它们的返回值都是double,参数类型也是double,因此可以经过包装器包装为function<double<double>>

如下:

function<double(double)> func1 = func;
function<double(double)> func2 = Functor();
function<double(double)> func3 = lambadaFunc;

此时,三者的类型就都是function<double(double)> 了。

有了这一层包装器,在需要统一管理函数时,就很方便了。比如说我现在要搞一个计算器的map,往map中输入哪一个操作符,就调用哪一个函数:

map<char, function<int(int, int)>> opFuncMap = {
    {'+', [](int x, int y) {return x + y; }},
    {'-', [](int x, int y) {return x - y; }},
    {'*', [](int x, int y) {return x * y; }},
    {'/', [](int x, int y) {return x / y; }}
};

由于+ - * /的函数都是lambda表达式,四个表达式的类型都是不可知的,map的第二个模板参数就不知道是啥了。不过我们可以通过function进行包装,把所有函数都包装成function<int(int, int)>类型,最后就可以通过map统一管理了。

我们最后就可以这样调用函数:

opFuncMap['+'](1, 2);
opFuncMap['-'](1, 2);
opFuncMap['*'](1, 2);
opFuncMap['/'](1, 2);

好的,我来补充一下关于 function 包装器的常用操作:


function 的常用操作

  1. 拷贝和赋值:

    • function 对象支持拷贝构造和赋值操作,可以将一个 function 对象赋值给另一个。
    • 拷贝或赋值 function 对象时,会拷贝/赋值底层的可调用对象。
    • 例如:
      function<int(int)> f1 = [](int x) { return x * x; };
      function<int(int)> f2 = f1; // f2 现在也是一个 lambda 表达式
      
  2. 调用:

    • function 对象支持函数调用操作符 (),可以像调用普通函数一样调用 function 对象。
    • 调用 function 对象时,会调用底层的可调用对象。
    • 例如:
      function<int(int, int)> add = [](int x, int y) { return x + y; };
      int result = add(3, 4); // result 为 7
      
  3. 判空:

    • function 对象支持 bool 类型转换,可以用于判断 function 对象是否为空(未初始化)。
    • 例如:
      function<int(int)> f;
      if (!f) {
          cout << "f is empty" << endl;
      }
      
  4. 重置:

    • function 对象提供 reset() 成员函数,可以将 function 对象重置为未初始化状态。
    • 例如:
      function<int(int)> f = [](int x) { return x * x; };
      f.reset(); // f 现在为空
      

通过这些常用操作,我们可以更灵活地使用 function 包装器,满足各种需求。比如在需要统一管理不同类型的可调用对象时,function 就显得尤为有用。


bind

bind翻译后为绑定,其可以对参数进行绑定。其主要有两个功能:改变参数顺序给指定参数绑定固定值

语法
bind是一个函数模板,其接收多个参数,第一个参数为可调用对象,后续参数为该可调用对象的参数。这个参数的语法比较特别,C++11后新增一个命名空间域placeholders,其内部会存储很多变量,这些变量用于函数的传参,变量的名字为_x表示第x个参数。

比如以下代码中:

int sub(int a, int b)
{
    return a - b;
}

int main()
{
    auto f1 = bind(sub, placeholders::_2, placeholders::_1);

    f1(3, 5);

    return 0;
}

对于bind(sub, placeholders::_2, placeholders::_1);来说,sub这个参数是一个可调用对象。
placeholders::_2表示第二个参数,placeholders::_1表示第一个参数。

比如这个f1最后拿到了这个bind封装的函数,那么f1(3, 5)执行的并不是3 - 5,而是5 - 3

这是因为我们特地把placeholders::_2写在前面,f1(3, 5)把第二个5传给了placeholders::_2,把第一个3传给了placeholders::_1

而最后调用sub函数的时候,placeholders::_1会被传给sub的第一个参数,placeholders::_2则会传给sub的第而个参数。这样我们就完成了函数参数顺序的改变。

再比如以下代码:

int sub(int a, int b)
{
    return a - b;
}

int main()
{
    auto f2 = bind(sub, 3.14, placeholders::_1);

    f2(10);

    return 0;
}

bind(sub, 3.14, placeholders::_1)第一个参数为可调用对象sub,第二个参数是一个固定值3.14,那么如果通过f2调用该sub函数,参数a都固定为3.14。比如f2(10)就只传了一个参数,再去调用sub时,就完成3.14 - 10的操作。因此我们可以通过sub把某个参数绑定为固定值。

处理函数返回值

bind 函数还可以用来处理函数的返回值。示例如下:

int add(int x, int y) {
    return x + y;
}

int main() {
    // 绑定 add 函数,并将返回值乘以 2
    auto doubleAdd = bind([](int result) { return result * 2; }, add(placeholders::_1, placeholders::_2));
    int result = doubleAdd(3, 4);
    cout << result << endl; // 输出 14
    
    return 0;
}

在这个例子中,我们使用 bindadd 函数的返回值传递给一个 lambda 表达式,该 lambda 表达式将返回值乘以 2。最终,doubleAdd 函数会返回 add 函数返回值的两倍。

通过这种方式,我们可以对函数的返回值进行各种处理,如格式化、取模等,从而实现更复杂的功能。

总之,bind 不仅可以用于改变参数顺序和绑定参数,还可以用于绑定成员函数以及处理函数返回值,是一个非常强大的工具。