• C++ function<>和bind()


    一、可调用对象

    介绍两个概念:调用运算符和可调用对象

    • 调用运算符

           调用运算符,即:() 。跟随在函数名之后的一对括号 “()”,起到调用函数的效果,传递给函数的参数放置在括号内。

    • 可调用对象

            对于一个对象或者一个表达式,如果可以对其使用调用运算符,则这个对象或者表达式可被称为可调用对象。所以可调用对象看起来就是可以像函数一样调用的对象。

             分类

         可调用对象可以分为如下几类:

    • 普通函数和函数指针
    • 仿函数(functor(仿函数), 或者称之为function object(函数对象))

              仿函数(functor)是一个结构体或者类,只不过它重载了'()'操作符,使得这个结构体或者类可以像函数一样被调用。仿函数也叫函数对象。

    • lambda函数
    • 类成员函数指针或者类成员指针

    二、std::function

          可调用对象的定义方式比较多,但是函数的调用方式都是类似的。比如:经常遇到将可调用类型作为参数传递的情况,如果针对不同的可调用类型进行单独声明,则函数参数只能接收某种具体类型,这非常的不灵活,所以需要使用一种统一的方式保存可调用对象或者传递可调用对象,于是就有了std::function。

         std::function是一个可调用对象包装器,它是一个类模板,它通过指定模版参数,用统一的方式来处理各种可调用对象。

    std::function var_name;

        其模板参数是函数签名:比如,void()表示一个函数,不接收参数,也不接收返回值;int(int,int)代表某函数,它接收两个参数并返回int值。假设我们要构建function<>实例,那么,由于模板参数先行指定了函数签名,因此指向的函数必须与之相符。即它应该接收指定类型的参数,返回值也必须可以转换为指定类型的可调用对象。

         std::function是定义在 这个头文件中的,所以如果要想使用std::function则需要先包含这个头文件:#include

    1.包装普通函数或者函数指针

    1. #include
    2. #include
    3. using namespace std;
    4. int add(int a, int b) {
    5. return a + b;
    6. }
    7. int main() {
    8. //普通函数
    9. function<int(int, int)> f = add;
    10. cout << "sum is " << f(5, 8) << endl;
    11. //定义一个函数指针,并将其指向一个函数
    12. int (*fptr)(int, int);
    13. fptr = add;
    14. function<int(int, int)> ftr = fptr;
    15. cout << "(fptr)sum is " << f(5, 79) << endl;
    16. return 0;
    17. }

    2. 包装仿函数

    下面给出了一个实例代码, 定义了一个class,名叫AddTwoNum,因为其重载了‘()’操作符,所以这个类定义的实例对象是一个仿函数 (或叫函数对象),在本例中,这个仿函数返回值是其两个int类型成员的数值之和。

    1. #include
    2. #include
    3. using namespace std;
    4. class AddTwoNum {
    5. public:
    6. int operator()() { return a + b; }
    7. AddTwoNum(int x, int y) :a(x), b(y) {}
    8. AddTwoNum() { a = 0, b = 0; }
    9. private:
    10. int a;
    11. int b;
    12. };
    13. int main() {
    14. AddTwoNum add(100,200);
    15. function<int(void)> f = add;
    16. cout << f() << endl;
    17. return 0;
    18. }

    3. 包装lamda函数

    1. #include
    2. #include
    3. using namespace std;
    4. int main() {
    5. function<int(int, int)> f1 ;
    6. f1 = [](int a, int b) {
    7. return a + b; };
    8. cout << f1(10, 50) << endl;
    9. return 0;
    10. }

    4. 包装类的静态成员函数

            因为class的静态成员函数不需要类对象实例或者类对象指针也可以调用,所以function也可以包装类的静态成员函数。

    1. #include
    2. #include
    3. using namespace std;
    4. class AddTwoNum {
    5. public:
    6. static int Sum(int a, int b) { return a + b; }
    7. };
    8. int main() {
    9. function<int(int, int)> f = &AddTwoNum::Sum;
    10. cout << f(50, 80) << endl;
    11. return 0;
    12. }

    5. 包装类的普通成员函数

           function是无法直接包装类的普通成员函数,因为类普通成员函数指针是需要类对象参与才能完成的但是结合后面要介绍的bind()一起是可以实现包装类的普通成员函数的,这个在后面bind()那一节会看到。

    std::function对象实例可被拷贝和移动,并且可以使用指定的调用特征来直接调用目标元素。当std::function对象实例未包含任何实际可调用实体时,调用该std::function对象实例将抛出std::bad_function_call异常。  

    三、std::bind()

           std::bind()是一个通用的函数适配器,它也是包含在这个头文件里的。std::bind()的使用方式如下面所示,std::bind将可调用对象与其参数一起进行绑定,生成一个新的可调用对象来适应原对象的参数列表,绑定后的结果可以使用前面介绍的std::function保存。这时我们调用 newCallable,newCallable 就会调用 callable, 并用 arg_list 传递参数。

        auto newCallable = bind(callable,  arg_list);

             另外,如果函数有多个参数,bind()可以绑定部分参数,其他的参数可以等到调用时指定。bind()在绑定参数时需要通过占位符std::placeholder::_x来决定bind()参数列表里所在的位置的参数在调用发生时将会属于第几个参数:_x表示外部传参时,调用者所传的实参列表里的第x个参数需要作为形参所在的这个位置的参数,下面举个例子: 

              下面这个例子是一个将参数列表里的参数打印出来的一个函数printF(),我们通过bind()绑定其部分参数,和参数顺序来生成几个不同的新的可调用对象。

    • f1其实和原先的printF没有差别,参数个数和参数顺序都是一样的;
    • f2有先绑定第三个参数,即第三个参数已经指定为一个固定的值--->3,所以新生成的可调用对象在调用时,只需要传入前两个参数的值即可;
    • f3虽然也要像原先的printF()那样传入三个参数,可是由于placeholders有改变参数的顺序,以传入的第一个参数为例,因为f3的第1个参数有标明placeholders::_3,这就表示,调用f3()时传入的第3个参数会被作为原先可调用对象printF()的第一个参数。
    • f4也是通过palceholders改变了原先的参数顺序,而且还先绑定了第2个参数的值为3
    1. #include
    2. #include
    3. using namespace std;
    4. void printF(int x, int y, int z) {
    5. cout << "x="<", y=" << y << ", z=" << z<
    6. }
    7. int main() {
    8. auto f1 = bind(printF,placeholders::_1, placeholders::_2, placeholders::_3);
    9. cout << "f1(1,2,3)-----> "; //x=1 ,y=2, z=3
    10. f1(1, 2, 3);
    11. auto f2 = bind(printF, placeholders::_1, placeholders::_2, 3);
    12. cout << "f2(1,2) -----> "; //x=1 ,y=2, z is always 3
    13. f2(1, 2);
    14. auto f3 = bind(printF, placeholders::_3, placeholders::_1, placeholders::_2);
    15. cout << "f3(1,2,3)-----> "; //x=3 ,y=1, z=2
    16. f3(1, 2, 3);
    17. //
    18. auto f4 = bind(printF, placeholders::_2, 3, placeholders::_1);
    19. cout << "f4(1,2) -----> "; //x=2 ,y is always 3, z=1
    20. f4(1, 2);
    21. return 0;
    22. }

    bind()绑定类的普通成员函数

    bind()如果绑定的类的普通成员函数的话,需要传入该类的类对象或者类对象指针 

    1. #include
    2. #include
    3. using namespace std;
    4. class AddTwoNum {
    5. public:
    6. int Sum() { return a + b; }
    7. AddTwoNum(int x, int y) :a(x), b(y) {}
    8. AddTwoNum() { a = 0, b = 0; }
    9. private:
    10. int a;
    11. int b;
    12. };
    13. int main() {
    14. AddTwoNum add(100, 200);
    15. //对象形式调用成员函数
    16. auto f1 = bind(&AddTwoNum::Sum,add);
    17. cout << f1() << endl;
    18. //指针形式调用成员函数
    19. auto f2 = bind(&AddTwoNum::Sum, &add);
    20. cout << f2() << endl;
    21. return 0;
    22. }

    bind()绑定类的静态成员函数

          因为即使是在没有实例化的类对象的条件下,类的static成员函数也是可以被调用的,所以bind()绑定的是类的静态成员函数时,不用指定类对象或类对象指针。

    1. #include
    2. #include
    3. using namespace std;
    4. class AddTwoNum {
    5. public:
    6. static int Sum(int a,int b) { return a + b; }
    7. };
    8. int main() {
    9. auto f1 = bind(&AddTwoNum::Sum, placeholders::_1, placeholders::_2);
    10. cout << f1(20,30) << endl;
    11. return 0;
    12. }

  • 相关阅读:
    【CSS】5分钟带你彻底搞懂 W3C & IE 盒模型
    整体记录一下asp.net core 认证和授权
    GET 和 POST 方式区别
    狼的传说小游戏
    滚雪球学Java(09-2):Java中的关系运算符,你真的掌握了吗?
    C++ 【继承】
    web前端期末大作业:基于HTML+CSS+JavaScript制作我的音乐网站(带设计报告)
    【Leetcode】1829. Maximum XOR for Each Query
    Servlet(一)
    spark读取hive表字段,区分大小写问题
  • 原文地址:https://blog.csdn.net/weixin_42463482/article/details/132917074