【C/C++开发】函数指针与回调函数

C++很多类库都喜欢用回调函数,MFC中的定时器,消息机制,hook机制等待,包括现在在研究的cocos2d-x中也有很多的回调函数。


1.回调函数

什么是回调函数呢?回调函数其实就是一个通过函数指针调用的函数!假如你把A函数的指针当作参数传给B函数,然后在B函数中通过A函数传进来的这个指针调用A函数,那么这就是回调机制。A函数就是回调函数,而通常情况下,A函数是系统在符合你设定条件的情况下会自动执行,比如Windows下的消息触发等等。那么调用者和被调用者的关系就被拉开了,就像是中断处理函数那样。


2.函数指针

函数指针是一个指针,只是这个指针它不像普通的指针指向是是一个变量,此时它指向的是一个函数,也就是它存储的是一个函数的地址,如果我们愿意的话,可以改变这个它的值,让他由指向funA转变为指向funB,那么这个函数指针的作用就改变了。


3.函数指针的使用


3.1函数指针声明

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


3.2示例

  1. typedef void(*Fun)(int,int); //定义函数指针类型
  2. void min(int a,int b);
  3. void max(int a,int b);
  4. void min(int a,int b)
  5. {
  6. int minvalue=a<b?a:b;
  7. std::cout<<"min value is "<<minvalue<<"\n";
  8. }
  9. void max(int a,int b)
  10. {
  11. int maxvalue=a>b?a:b;
  12. std::cout<<"Max value is "<<maxvalue<<"\n";
  13. }
  14. int _tmain(int argc, _TCHAR* argv[])
  15. {
  16. Fun pFun=NULL; //定义函数指针变量pFun
  17. //pFun=min;//两种赋值方式都支持
  18. pFun=&min;
  19. pFun(1,2);//这里获得最小值
  20. //pFun=max;
  21. pFun=&max;
  22. pFun(1,2);//这里获得最大值
  23. return 0;
  24. }
我想这么写应该是很一目了然了。


4.回调函数的使用

回调函数的使用其实和上面函数指针示例是很一致的,只是上面的实例中pFun是我们自己调用的。现在我们在MFC中让系统调用一下吧。


先看一下SetTimer函数声明:

UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );
其中lpfnTimer是这么解释的:Specifies the address of the application-supplied TimerProc callback function that processes the WM_TIMER messages. If this parameter is NULL, the WM_TIMER messages are placed in the application's message queue and handled by the CWnd object.

也就是或如果为NULL的话,系统自动触发WM_Timer消息,然后调用OnTimer函数。


我们调用自己的自定义回调函数。

  1. CALLBACK VOID callback_fun(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
  2. {
  3. TRACE("CallBack\n");//debug下运行控制台输出CallBack
  4. }
  5. void CTestCallBackDlg::OnBnClickedButton1()
  6. {
  7. // TODO: 在此添加控件通知处理程序代码
  8. SetTimer(1,1000,(TIMERPROC)callback_fun);//每一秒回调一次
  9. }

另外,需要注意的是回调函数必须是全局函数或者静态成员函数,因为普通的成员函数会隐含着一个传递函数作为参数,也就是this指针。因此如果使用普通成员函数作为回调函数的话会导致函数参数个数不匹配,因此编译失败。这也是线程函数是多为静态函数的原因。

我们还注意到回调函数用CALLBACK修饰,我们可以在windef.h中发现:

#define CALLBACK    __stdcall

CALLBACK其实就是__stdcall,还记得上篇讲过的函数调用约定吗?


如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过。
其错误是普通的C++成员函数都隐含了一个传
递函数作为参数,亦即“this”指针,C++通过传递this指针给其成员函数从而实现成员函数可以访问C++的数据成员。这也可以理解为什么C++类的多个实例可以共享成员函数却-有不同的数据成员。由于this指针的作用,使得将一个CALL-BACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败。要解决这一问题的关键就是不让this指针起作用,通过采用以下两种典型技术可以解决在C++中使用回调函数所遇到的问题。这种方法具有通用性,适合于任何C++。   
   
  1).   不使用成员函数,为了访问类的成员变量,可以使用友元操作符(friend),在C++中将该函数说明为类的友元即可。       
  2).   使用静态成员函数,静态成员函数不使用this指针作为隐含参数,这样就可以作为回调函数了。静态成员函数具有两大特点:其一,可以
在没有类实例的情况下使用;其二,只能访问静态成员变量和静态成员函数,不能访问非静态成员变量和非静态成员函数。由于在C++中使用类成员函数作为回调函数的目的就是为了访问所有的成员变量和成员函数,如果做不到这一点将不具有实际意义。解决的办法也很简单,就是使用一个静态类指针作为类成员,通过在类创建时初始化该静态指针,如pThis=this,然后在回调函数中通过该静态指针就可以访问所有成员变量和成员函数了。

这种处理办法适用于只有一个类实例的情况,因为多个类实例将共享静态类成员和静态成员函数,这就导致静态指针指向最后创建的类实例。为了避免这种情况,可以使用回调函数的一个参数来传递this指针,从而实现数据成员共享。这种方法稍稍麻烦,这里就不再赘述。关于静态方法访问非静态变量和函数的方式请见http://www.cnblogs.com/this-543273659/archive/2011/08/29/2157966.html

首先明白什么是回调函数:比如说被调函数void callbackf(int n){}要想作为回调函数的话,callbackf必须作为主调函数的形参出现,如void f(void (*p(int)),int n)形式才行!

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

如果我要在static中调用类中的成员,怎么做?
我得定义一个全局的 指向该类的指针,然后在类的初始化函数里面将 类的this 指针赋值给该全局指针。
然后在static函数中通过该全局指针调用类的其它成员。

例子1:

复制代码
#include "stdafx.h"
#include <iostream>
#include <assert.h>
using namespace std;

class Test
{
public:

    friend void callBackFun(void){ cout << "CallBack Function!";} //友元函数作为回调函数 friend方式实现,											  //因为callBackFun默认有一个const Test* 的指针
};

typedef void (*FPtr)(void);

void Fun(FPtr ptr)
{
    ptr();
}



int main(void)
{
    Fun(callBackFun); 

    return 0;
}

例2:

#include<iostream> using namespace std; class A { public:    static void callback()  //类的成员函数作为回调函数 static方式实现  {   cout<<"回调函数开始执行了!"<<endl;  } }; void f(void (*p)())  {   p();

 } int main() {  void (*p)();  p=A::callback;  f(p);  return 0; }

还可以把f()函数设为类的成员函数:

#include<iostream> using namespace std; class A { public:    static void callback()  //类的成员函数作为回调函数 static方式实现  {   cout<<"回调函数开始执行了!"<<endl;  }  void f(void (*p)())  {   p();

 } };

int main() {  A a;  void (*p)();  p=A::callback;  a.f(p);  return 0; }

///什么是回调函数
回调函数就是那些自己写的,但是不是自己来调,而是给别人来调用的函数。
消息响应函数就可以看成是回调函数,因为是让系统在合适的时候去调用。这不过消息响应函数就是为了处理消息的,所以就拿出来单做一类了。其实本质上就是回调函数。
但是回调函数不是只有消息响应函数一种,比如在内核编程中,驱动程序就要提供一些回调函数,当一个设备的数据读写完成后,让系统调用这些回调函数来执行一些后续工作。回调函数赋予程序员这样一种能力,让自己编写的代码能够跳出正常的程序控制流,适应具体的运行环境在正确的时间执行。
////////////////////////////////////////////
普通的函数是:咱们的函数调用系统的函数,
把你写的程序和系统已经封装好的函数看成两个部分
你的程序使用系统的函数 那叫 调用
系统函数使用你的程序函数 就叫回调
一般多用于系统函数与你的函数要进行异步处理
比如按键事件,其实是个消息
你的函数比按键事件更早存在
所以你要将这个函数做为回调函数提交给系统,
然后系统在接收到按键事件后,再调用你的函数
/////////////////////////////////////////////
比如:void fun(){printf();}
而回调函数是:系统调用你的函数。
win32 编程的WndProc,java的事件,c#的delegate都是这种思想。可以说没有坏处,回调使得系统更加灵活。
函数指针做为函数的参数,传递给一个被调用函数,
被调用函数就可以通过这个指针调用外部的函数,这就形成了回调

一般的程序中回调函数作用不是非常明显,可以不使用这种形式

最主要的用途就是当函数不处在同一个文件当中,比如动态库,要调用
其他程序中的函数就只有采用回调的形式

#include "stdio.h"
#include "conio.h"

int add(int a, int b);
int libfun(int (*pDis)(int a, int b));

int main(void)
{
int (*pfun)(int a, int b);

pfun = add;
libfun(pfun);//调用int libfun(int (*pDis)(int a, int b))


}

int add(int a, int b)
{
return a + b;

}

int libfun(int (*pDis)(int a, int b))//回调add()函数
{
int a, b;
a = 1;
b = 2;
printf("%d", pDis(a, b));

}

现在这几个函数是在同一个文件当中

假如 
int libfun(int (*pDis)(int a, int b))
是一个库中的函数,就只有使用回调了,通过函数指针参数将外部函数地址传入
来实现调用

函数 add 的代码作了修改,也不必改动库的代码,就可以正常实现调用
便于程序的维护和升级

摘要:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。


按照上面的说法,实现一个C Language的回调函数如下,

  1. #include <stdio.h>
  2. //回调函数
  3. int ADD(int (*callback)(int,int), int a, int b){
  4. return (*callback)(a,b);//此处回调add函数...
  5. }
  6. //普通函数
  7. int add(int a, int b){
  8. return a + b;
  9. }
  10. int main(void){
  11. printf("%d\n",add(1,2));
  12. printf("%d\n",ADD(add,1,2));
  13. return 0;
  14. }

从上面的定义及其实现可以看出,回调函数必须有函数指针的存在,而这里函数指针一般可以先typedef一下,然后再使用,

一般格式: 返回值 (*指针名) (参数列表)

  1. #include <stdio.h>
  2. //返回值(*指针名)(参数列表)
  3. typedef int (*callback)(int,int);
  4. //回调函数
  5. int ADD(callback p, int a, int b){
  6. return (*p)(a,b);//此处回调add函数...
  7. }
  8. //普通函数
  9. int add(int a, int b){
  10. return a + b;
  11. }
  12. int main(void){
  13. printf("%d\n",add(1,2));
  14. printf("%d\n",ADD(add,1,2));
  15. return 0;
  16. }

可是,根据上面的例子,回调函数搞得这么麻烦,貌似并没有什么大作用.....纠结!

别纠结,来看一下库函数中的sort排序是怎么弄的。algorithm它提供了某些排序算法的实现(如冒泡排序、快速排序、shell排序、shake排序等等),为了能让库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,能让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调,如下:

  1. #include <stdio.h>
  2. #include <algorithm>
  3. bool cmp(int a, int b){
  4. return a > b;
  5. }
  6. int main(void){
  7. int a[8] = {5,43,1,7,8,13,0,74};
  8. std::sort(a,a+10,cmp);//callback
  9. return 0;
  10. }
这里只是提及了一下库函数中的排序问题,如果想了解更多,可以去看一下《泛型编程与C++标准模板库 : 浅谈sort()排序函数》这篇文章。




阅读更多
换一批

没有更多推荐了,返回首页