C++回调函数的定义和调用

 

一、C++回调函数

1. C/C++回调函数

首先看一下回调函数的官方解释:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。这段解释比较官方。个人可以简单的理解为:**一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。**如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调。

2. 普通回调

int Callback_1(int a)   ///< 回调函数1
{
    printf("Hello, this is Callback_1: a = %d \n", a);
    return 0;
}

int Callback_2(int b)  ///< 回调函数2
{
    printf("Hello, this is Callback_2: b = %d \n", b);
    return 0;
}

int Callback_3(int c)   ///< 回调函数3
{
    printf("Hello, this is Callback_3: c = %d \n", c);
    return 0;
}

int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{
    return Callback(x);
}

int main()
{
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

执行结果:

  Hello, this is Callback_1: a = 4
  Hello, this is Callback_2: b = 5
  Hello, this is Callback_3: c = 6

在这个入门案例中,Callback_1、2、3就是回调函数,handle函数的第二个参数就是函数指针,也就是通过函数指针来调用。纯C语言通过函数指针来进行回调函数的调用,C++则可以通过引用、Lambda等多种方式来进行,下面进行具体的介绍。

3. 函数指针

首先函数指针也是一种指针,只不过指向的是函数(C语言中没有对象)。然后通过这个指针就可以调用。

int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
p = Func;          /*将Func函数的首地址赋给指针变量p*/
p = &Func;          /*将Func函数的首地址赋给指针变量p*/

经过上述后,指针变量 p 就指向函数 Func() 代码的首地址了。下面看一个具体的例子。

int Max(int x, int y)  //定义Max函数
{
    if (x > y){
        return x;
    }else{
      return y;
    }
}
int main()
{
  int(*p)(int, int);  //定义一个函数指针
  p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
  int c= (*p)(1,2);//通过函数指针调用Max函数
  printf("%d",c);  
  return 0;
}

p指向Max函数之后,然后用p调用Max函数,返回两个数中的最大值。特别注意的是,因为函数名本身就可以表示该函数地址(指针),因此在获取函数指针时,可以直接用函数名,也可以取函数的地址。

p = Max可以改成 p = &Max;
c = (*p)(a, b) 可以改成 c = p(a, b)

所以函数指针的通常写法是

函数返回值类型 (* 指针变量名) (函数参数列表);

在这里指针变量名也可以叫做函数名,但是通常可以用typedef进行描述

typedef 函数返回值类型 (* 指针变量名) (函数参数列表);

最后需要注意的是,指向函数的指针变量没有 ++ 和 – – 运算。

4. C++类的静态函数作为回调函数

前面函数指针的方式作为回调函数的一种方式,可以同时用于C和C++,下面介绍另外的一些方式,因为C++引入了对象的概念,可以使用类的成员和静态函数作为回调函数。

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);
}

在类B中调用类A中的静态函数作为回调函数,从而实现了回调。但这种实现有一个很明显的缺点:static 函数不能访问非static 成员变量或函数,会严重限制回调函数可以实现的功能

5. 类的非静态函数作为回调函数

  • C++类的成员函数作为C库的回调函数:见demo库

这种方式比较麻烦,可以先看一下下面的例子。

class ProgramA2 {
 public:
  void FunA1() { printf("I'am ProgramA2.FunA1() and be called..\n"); }
  void FunA2() { printf("I'am ProgramA2.FunA2() and be called..\n"); }
};

class ProgramB2 {
 public:
  void FunB1(void (ProgramA2::*callback)(), void *context) {
    printf("I'am ProgramB2.FunB1() and be called..\n");
    ((ProgramA2 *)context->*callback)();
  }
};
int main(int argc, char **argv) {
  ProgramA2 PA2;
  PA2.FunA1();
  ProgramB2 PB2;
  PB2.FunB1(&ProgramA2::FunA2, &PA2);  // 此处都要加&
}

功能总体与上面一个相同,但是,类的回调的非静态函数本身不属于该类,所以和普通函数作为回调函数类似。这种方式存在一些不足,,也就我预先还要知道回调函数所属的类定义,当ProgramB想独立封装时就不好用了。(违背了一些设计模式的原则)

6. Lambda表达式作为回调函数

Lambda本身就是一种匿名函数,是一种函数的简写形式(此处参考上一篇博客Lambda表达式)

#include <iostream>
#include<functional>
void func1(int a,std::function<void(int)> func2){
  func2(a);
}
int main(int argc, char **argv) {
  auto fun3 = [](int a){
    std::cout<<a<<std::endl;
  };
  func1(3,fun3);
}

这种方式也较为简单,但要注意在C++11版本才开始引入Lambda表达式,在一些较为老旧的编译器上可能无法通过。

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

这种方式也是适用于C++,要引入functional的头文件。存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。std::bind()函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。

#include <iostream>

#include <functional> // 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));
}

主要看最后一行,通过std::bind函数绑定了对象与对应的函数,这种方式比上面的通过类的成员函数进行回调更为简单方便。下面看一下如果有参数的话,需要引入占位符std::placeholders::_1来进行回调。


#include <iostream>
#include <functional>
using namespace std;
 
int TestFunc(int a, char c, float f)
{
    cout << a << endl;
    cout << c << endl;
    cout << f << endl;
 
    return a;
}
 
int main()
{
    auto bindFunc1 = bind(TestFunc, std::placeholders::_1, 'A', 100.1);
    bindFunc1(10);
 
    cout << "=================================\n";
 
    auto bindFunc2 = bind(TestFunc, std::placeholders::_2, std::placeholders::_1, 100.1);
    bindFunc2('B', 10);
 
    cout << "=================================\n";
 
    auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
    bindFunc3(100.1, 30, 'C');
 
    return 0;
}

 

上述例子中引入了占位符std::placeholders::_1,可以有多个,通过下划线加数字来实现,从而实现有参数的回调。这个bind函数中的重载通常第一个是函数的指针,第二个是调用对象的指针,后面跟上参数占位符。

二、其他参考资料

 

【出处】:https://blog.csdn.net/mayue_web/article/details/127918967

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

1、函数指针

概念

一个程序运行时,所有和运行相关的资源都需要被加载到内存中,如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

使用函数指针实现函数调用

复制代码
 1 #include <iostream>
 2  3 typedef void (*PINVOKE)(const char *str);
 4  5 void Invoke(const char *str)
 6 {
 7     std::cout << str << std::endl;
 8 }
 9 10 int main()
11 {
12     PINVOKE fp = Invoke;
13     fp("Hello world.");
14     
15     return 0;
16 }
复制代码

说明

函数指针与函数声明的唯一区别就是用指针名(*fp)代替了函数名Invoke,然后进行赋值fp = Invoke就可以进行函数指针的调用了。声明函数指针时要求函数返回值类型、参数个数、参数类型等与已定义函数保持一致。注意,函数指针必须用括号括起来 void (*fp)(char *s)

2、回调函数

概念

声明并定义一个函数A,然后把函数A的指针作为参数传入其他的函数(或系统)中,其他的函数(或系统)在运行时通过函数指针调用函数A,这就是所谓的回调函数。简单来说:回调函数就是一个通过函数指针调用的函数。

示例

复制代码
 1 #include <iostream>
 2  3 typedef void (*CALLBACKFUN)(const char *str);
 4  5 void PrintText(CALLBACKFUN fp, const char *str)
 6 {
 7     fp(str);
 8 }
 9 10 void Invoke(const char *str)
11 {
12     std::cout << str << std::endl;
13 }
14 15 int main()
16 {
17     PrintText(Invoke, "Hello world.");
18     return 0;
19 }
复制代码

类成员函数作为回调函数

回调函数是基于C - Windows SDK的技术,不是针对C++的,程序员可以将一个C函数直接作为回调函数,但是如果试图直接使用C++的成员函数作为回调函数将发生错误,因为普通的C++成员函数都隐含一个传递函数作为参数,即this指针,C++通过向其他成员函数传递一个指向自身的指针来实现程序函数访问C++数据成员。所以实现类成员函数作为回调函数有两种途径:1、不使用成员函数(使用友元操作符friendC函数访问类的数据成员);2、使用静态成员函数。

例如:

复制代码
 1 #include <iostream>
 2  3 class CPrintString
 4 {
 5 public:
 6     void PrintText(const char *str)
 7     {
 8         std::cout << str << std::endl;
 9     }
10     
11     static void SPrintText(void *pPs, const char *str)
12     {
13         CPrintString *pThis = static_cast<CPrintString *>(pPs);
14         if(NULL == pPs)
15         {
16             return;
17         }
18         pThis->PrintText(str);
19     }
20 };
21 22 typedef void (*PRINTTEXT)(void *pPs, const char *str);
23 24 void CallBackFun(void *pPs, const char *str, PRINTTEXT fp)
25 {
26     fp(pPs, str);
27 }
28 29 int main()
30 {
31     CPrintString obj;
32     CallBackFun((void *)&obj, "Hello world.", CPrintString::SPrintText);
33     
34     return 0;
35 }
复制代码

3、为什么使用回调函数

一般情况下,回调函数能被普通函数替换,但回调函数最重要的作用是解耦,在这一特点上普通函数代替不了回调函数。

img

例子:

复制代码
 1 #include <stdio.h>
 2 #include <softwareLib.h>     // 包含 Library Function 所在读得 Software library 库的头文件
 3  4 int Callback()              // Callback Function
 5 {
 6     // TODO
 7     return 0;
 8 }
 9 10 int main()                  // Main program
11 {
12     // TODO
13     Library(Callback);
14     // TODO
15     return 0;
16 }
复制代码

乍一看,回调似乎只是函数间的调用,和普通函数调用没啥区别,但仔细一看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,并且丝毫不需要修改库函数的实现,这就是解耦。再仔细看看,主函数和回调函数是在同一层的,而库函数在另外一层,一般情况下库函数对开发人员并不可见,库函数的实现一般不会被修改,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那就只能通过传入不同的回调函数了,这在企业开发中非常常见。

 

【出处】:https://www.cnblogs.com/horacle/p/15572803.html

posted on 2023-11-25 17:23  jack_Meng  阅读(512)  评论(0编辑  收藏  举报

导航