• C++:回调函数的应用


    本文关于回调函数的学习的学习资料都来自于这里
    原文写得非常详细,所以将原文的大部分内容作为笔记摘抄了过来。

    1.回调函数

    回调函数指的是通过将一个函数作为参数传递给另一个函数,使得该函数能在特定事件发生或满足某个条件时被调用的机制。

    通常情况下,回调函数会在某个特定的上下文中被注册,并在满足特定条件时由相应的代码调用。这种机制常用于异步编程、事件处理、用户交互、错误处理等场景,以便将控制权从调用方传递给被调用方。

    以下是回调函数的一般使用方式:

    定义回调函数:定义一个函数,其签名和参数与所需的回调函数匹配。

    注册回调函数:将定义好的回调函数作为参数传递给需要使用回调函数的函数或组件。

    触发回调:当满足触发条件时,调用方会触发回调函数,并将必要的参数传递给回调函数。

    执行回调:回调函数被调用,执行相应的操作,并返回结果(如果有必要)。

    通过回调函数,可以实现不同模块之间的解耦,让调用方在满足特定条件时,将具体的行为交给被调用方来处理。这种灵活性和可扩展性使得回调函数成为处理异步和事件驱动的重要工具。

    1.1普通函数作为回调函数

    #include 
    
    void programA_FunA1() { printf("I'am ProgramA_FunA1 and be called..\n"); }
    
    void programA_FunA2() { printf("I'am ProgramA_FunA2 and be called..\n"); }
    
    void programB_FunB1(void (*callback)())
    {
      printf("I'am programB_FunB1 and be called..\n");
      callback();
    }
    
    int main(int argc, char **argv) 
    {
      programA_FunA1();
    
      programB_FunB1(programA_FunA2);//传入函数名
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    执行结果
    在这里插入图片描述

    1.2类的静态函数作为回调函数

    #include 
    
    class ProgramA {
     public:
      void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }
    
      static void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
    };
    
    class ProgramB {
     public:
      void FunB1(void (*callback)()) 
      {
        printf("I'am ProgramB.FunB1() and be called..\n");
        callback();
      }
    };
    
    int main(int argc, char **argv)
     {
      ProgramA PA;
      PA.FunA1();
    
      ProgramB PB;
      PB.FunB1(ProgramA::FunA2);//传入 域名::函数名
      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    执行结果:
    在这里插入图片描述
    可以看出,以上两种方式没有什么本质的区别。
    但这种实现有一个很明显的缺点:static 函数不能访问非static 成员变量或函数,会严重限制回调函数可以实现的功能。

    1.3类的非静态成员函数作为回调函数

    #include 
    
    class ProgramA {
     public:
      void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }
    
      void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
    };
    
    class ProgramB {
     public:
      void FunB1(void (ProgramA::*callback)(), void *context) 
      {
        printf("I'am ProgramB.FunB1() and be called..\n");
        ((ProgramA *)context->*callback)();
      }
    };
    
    int main(int argc, char **argv) 
    {
      ProgramA PA;
      PA.FunA1();
    
      ProgramB PB;
      PB.FunB1(&ProgramA::FunA2, &PA);  // 此处都要加&  传入(&域名::函数名,&对象)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    执行结果
    在这里插入图片描述
    这种方法可以得到预期的结果,看似完美,但是也存在明显不足。
    比如在programB中FunB1还使用 programA的类型,也就我预先还要知道回调函数所属的类定义,当programB想独立封装时就不好用了。

    1.4优化–非静态包装为静态

    这里还有一种方法可以避免这样的问题,可以把非static的回调函数 包装为另一个static函数,这种方式也是一种应用比较广的方法。

    #include 
    
    class ProgramA {
     public:
      void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }
    
      void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
    
      static void FunA2Wrapper(void *context) 
      {
        printf("I'am ProgramA.FunA2Wrapper() and be called..\n");
        ((ProgramA *)context)->FunA2();  // 此处调用的FunA2()是context的函数, 不是this->FunA2()
      }
    };
    
    class ProgramB {
     public:
      void FunB1(void (ProgramA::*callback)(), void *context) 
      {
        printf("I'am ProgramB.FunB1() and be called..\n");
        ((ProgramA *)context->*callback)();
      }
    
      void FunB2(void (*callback)(void *), void *context) 
      {
        printf("I'am ProgramB.FunB2() and be called..\n");
        callback(context);
      }
    };
    
    int main(int argc, char **argv) 
    {
      ProgramA PA;
      PA.FunA1();
    
      ProgramB PB;
      PB.FunB1(&ProgramA::FunA2, &PA);  // 此处都要加&
    
      printf("\n");
      PB.FunB2(ProgramA::FunA2Wrapper, &PA);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    执行结果
    在这里插入图片描述
    这种方法相对于上一种,ProgramB中没有ProgramA的任何信息了,是一种更独立的实现方式。
    FunB2()通过调用FunA2Wrapper(),实现间接的对FunA2()的调用。FunA2()可以访问和调用对类内的任何函数和变量。多了一个wrapper函数,也多了一些灵活性。
    上面借助wrapper函数实现回调,虽然很灵活,但是还是不够优秀,比如:
    1)多了一个不是太有实际用处的wrapper函数。
    2)wrapper中还要对传入的指针进行强制转换。
    3)FunB2调用时,不但要指定wrapper函数的地址,还要传入PA的地址。
    那是否有更灵活、直接的方式呢?有,可以继续往下看。

    2. std::funtion和std::bind的使用

    std::funtion和std::bind可以登场了。
    std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。std::bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。

    #include 
    
    #include  // fucntion/bind
    
    class ProgramA {
     public:
      void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }
    
      void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
    
      static void FunA3() { printf("I'am ProgramA.FunA3() and be called..\n"); }
    };
    
    class ProgramB {
      typedef std::function<void ()> CallbackFun;
     public:
       void FunB1(CallbackFun callback) 
       {
        printf("I'am ProgramB.FunB2() and be called..\n");
        callback();
      }
    };
    
    void normFun() { printf("I'am normFun() and be called..\n"); }
    
    int main(int argc, char **argv) 
    {
      ProgramA PA;
      PA.FunA1();
    
      printf("\n");
      ProgramB PB;
      PB.FunB1(normFun);
      printf("\n");
      PB.FunB1(ProgramA::FunA3);
      printf("\n");
      PB.FunB1(std::bind(&ProgramA::FunA2, &PA));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    执行输出:
    在这里插入图片描述

  • 相关阅读:
    月薪近万,工地转行测试,这一次的选择,真正实现了薪资翻倍~
    微服务注册到nacos,在服务列表中服务名与配置的名字不一致
    RocketMQ源码分析(十)之MappedFileQueue
    DockerFile构建过程解析
    Nginx map 实现时间格式转换
    【高级测试工程师必会系列】常用测试用例设计方法之状态迁移法
    伸展树(二) - C++实现
    golang gin单独部署vue3.0前后端分离应用
    DHCP学习
    半导体工艺控制设备1
  • 原文地址:https://blog.csdn.net/qq_43611366/article/details/136316524