回调函数,终于有个讲的明白的了

来源:互联网 发布:人工智能女机器人电影 编辑:程序博客网 时间:2024/05/16 09:39

所以,实现库函数时,库函数的一些功能如果想让用户自己去定制,那此时就留下一个回调函数的参数。

调用者调用库函数,库函数又反过来调用调用者自己写的函数,这个过程就叫回调。

 

简单说,看到一个函数(一般是库函数,普通函数或者自己写的某些api也可以)里的参数有函数指针,这个指针对应的函数就是回调函数。就是:参数有指针函数就是用了回调函数。

 

=====

一直没有看到一篇能把回调函数的作用讲的比较清楚的,一直都认为,回调函数不用也可以,用其它方式都可以实现同样的功能。

看到一篇文章,算是讲的比较清楚的:

C语言回调函数
定义:
被调者回头调用调用者的函数,这种由调用方自己提供的函数叫回调函数
应用场景举例:
现有一个快速排序算法,实现了快排算法的逻辑,但是快排算法中必须涉及数据大小的比较,为提高程序的通用性,掉用者提供一个比较函数,这样排序函数借此调用调用者的函数来比较大小。


应用详解:
回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被掉函数从而实现回调。因此要实现回调,必须首先定义函数指针,如:
void Func(char *s);函数原型
void (*pFunc)(char *);//函数指针
可以看出,函数的定义和函数指针的定义非常类似,一般来说,为了简化函数指针类型变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。
typedef void(*pcb)(char *) pcb;
被调函数的例子:
void GetCallBack(pcb callBack)
{
/*do something*/
}
用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:
void fCallBack(char *s)
{
/*do something*/
}
然后,就可以直接把fCallBack当作一个变量传递给GetCallBack,
GetCallBack(fCallBack)
如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。
  (2 )参数传递规则
到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或 者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。
  将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:
  // 被调用函数是以int为参数,以int为返回值
  __stdcall int callee(int);
  // 调用函数以函数指针为参数
  void caller( __cdecl int(*ptr)(int));
  // 在p中企图存储被调用函数地址的非法操作
  __cdecl int(*p)(int) = callee; // 出错
  指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列
  

 

(3 )应用举例
  C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过程。如常用的快速排序函数、二分搜索函数等。
  快速排序函数原型:
void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
  二分搜索函数原型:
void *bsearch(const void *key, const void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
  其中fcmp就是一个回调函数的变量

下面给出一个具体的例子:
#include<stdio.h>
#include<stdlib.h>


int sort_function( const void *a, const void *b);

int list[5] = { 54, 21, 11, 67, 22 };

int main(void)
{
int x;
qsort((void *)list, 5, sizeof(list[0]), sort_function);
for (x = 0; x<5; x++)
printf("%i\n", list[x]);
return 0;
}


int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}

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

总结:想了解回调函数的作用是什么,就得知道回调函数是在什么情况下才会采用。

由这篇文章看出,在实现一些库函数时,比如文中为了提高库函数的通用性:

现有一个快速排序算法,实现了快排算法的逻辑,但是快排算法中必须涉及数据大小的比较,为提高程序的通用性,掉用者提供一个比较函数,这样排序函数借此调用调用者的函数来比较大小。

比较函数让用户自己定制,这样用户可以根据数据类型自己定义如何比较大小,甚至可以比较字符长,这样就使排序算法可以适用各种数据类型,提高了程序的通用性。
所以,实现库函数时,库函数的一些功能如果想让用户自己去定制,那此时就留下一个回调函数的参数。

调用者调用库函数,库函数又反过来调用调用者自己写的函数,这个过程就叫回调。

 

当然在库函数里直接调用比较大小的函数也行,但是你得告诉使用者去实现这个函数,而在库函数里的参数里显示的指出我需要一个函数会更加明显。

 

库函数只关心回调函数的返回值,根据返回值来判断做出什么操作。不关心回调函数的具体实现。如果说因为这点把被调用者和调用者分开,我觉得牵强,你随便调用个函数都是不用关心函数的内部实现,都是只关心返回值。

 

有些常用比如来讲解回调函数的,其实没讲到点子上,例如:

精妙比喻:回调函数还真有点像您随身带的BP机:告诉别人号码,在它有事情时Call您。

其实你不用回调函数,用其它的普通函数一样可以实现这样的功能。我觉得用回调函数而不用普通函数的唯一区别实际就是因为库函数的参数可以显示的告诉用户你需要实现我参数里的这个函数。我觉得其实就这么一个优点,其它的所谓什么回调函数的优点其实用普通函数一样可以实现。

原创粉丝点击