精华内容
下载资源
问答
  •  我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢...

     

     

    一回调函数

      我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?

      使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。

      而 那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的 API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK(相当于FAR PASCAL),这主要是说明该函数的调用方式。

      至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。

      也可以这样,更容易理解:回调函数就好像是一个中断处理函数,系统在符合你设定的条件时自动调用。为此,你需要做三件事:

      1. 声明;

      2. 定义;

      3. 设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。

      声明和定义时应注意:回调函数由系统调用,所以可以认为它属于WINDOWS系统,不要把它当作你的某个类的成员函数。

      二回调函数、消息和事件例程

      调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。 CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用 者和被调者可以互不相识,于是才有了后来的函数和构件。

    此调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调 用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。更通行的办法是 在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值 说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故 称其为回调(callback)。

      回调函数使程序结构乱了许多。Windows API 函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。

      无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。

      Windows 系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是 Windows 的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的 WParam 和 LParam 相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等 Windows 发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦 启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程 B 收到进程 A 发来的消息,启动了一段代码,其中又向进程 A 发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好 终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。

     

    利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口 handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借 API 函数 SendMessage 向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消 息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低 速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借 API 函数 PostMessage 发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。

      如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开 object。但 object 并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序 要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调 用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。

      不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的 东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),其代价是降低了程序效率。事件例程(?)使编程者无需直接操作地址,但并不会使程序减速。

    (例程似乎是进程的台湾翻译。)

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

      回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了 完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,他们的共性都是 跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知 的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用 时,只需引用这个函数指针和相关的参数指针。 其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。

      四调用方式

      软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕 才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它 的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用 回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础。

    对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除 了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。

      对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

      在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。

      Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。

      对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。

      下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。

      过程语言中的回调(C)

      (1 )函数指针

      回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:

    void Func(char *s);// 函数原型

      void (*pFunc) (char *);//函数指针

      可以看出,函数的定义和函数指针的定义非常类似。

      一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。

    typedef void(*pcb)(char *);

      回调函数可以象普通函数一样被程序调用,但是只兴 坏弊鞑问 莞 坏骱 辈拍艹谱骰氐骱 ?

      被调函数的例子:

    void GetCallBack(pcb callback)
    {

    }
    用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:
    void fCallback(char *s)
    {

    }

      然后,就可以直接把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;
    }

    转载于:https://www.cnblogs.com/SpadeA/p/5122974.html

    展开全文
  • 用函数指针实现回调函数,除了可以使用函数指针简化函数的...如果把函数的指针作为参数传递给另一个函数,那么当这个函数指针用于调用它所指向的函数时,就说这个函数是回调函数。 函数指针已经够复杂了,为什么还...

    用函数指针实现回调函数,除了可以使用函数指针简化函数的调用之外,函数指针更大的用途在于它可以作为函数参数传递给某个函数,从而实现函数的回调。所谓函数的回调,就是在某个函数中,通过函数指针调用另外一个函数,而这个函数指针,大多数情况下是通过函数参数传递进来的。如果把函数的指针作为参数传递给另一个函数,那么当这个函数指针用于调用它所指向的函数时,就说这个函数是回调函数。

    f24b527f6f8479fd4f5a3ece3d853a10.png

    函数指针已经够复杂了,为什么还要使用它作为函数参数进行函数的回调?程序简单一点不是更好吗? 回调函数的使用,正是为了让程序变得更简单。因为回调函数可以把主调函数与被调函数分开,主调函数不必关心谁是被调函数,所有它需要知道的只是存在一个具有某种特定原型、某些限制条件的被调函数。这就像在主调函数中留下了一个插口,它定义了插口的规则,也就是函数的返回值和具体的参数。而被调函数就是插头,可以把任何符合这个插口规则的插头插入这个插口中,从而实现整个主调函数。也可以通过改变插入的插头来改变主调函数的功能,从而改变主调函数的实现。

    C/C++学习交流:点击进入

    a3dbff1a005d4157311e976f45e41a8a.png

    使用回调函数可以实现同一个算法框架、不同的算法实现,最终达到算法的通用。 还是回到上面的例子,看看如何使用回调函数让程序更具灵活性、更加通用。假设现在要求在每条打印消息的前后还要打印一些符号作为装饰,为了完成这项任务,可以定义一个统一的打印消息函数。
    // 定义函数指针类型
    typedef void (* PRINTFUNC )(int);
    // 统一的打印消息函数
    void PrintMessage( int nSocre , PRINTFUNC pFunc )
    {
    cout<<"============"<<endl;
    // 通过函数指针回调函数
    (*pFunc)(nSocre);
    cout<<"++++++++++++"<<endl;
    }
    // 主函数
    int _tmain(int argc, _TCHAR* argv[])
    {
    int nScore = 22;
    PRINTFUNC pFunc;
    // 根据不同分数给pFunc赋值
    // …
    // 使用不同函数指针作为参数调用PrintMessage()函数
    PrintMessage( nScore, pFunc );
    return 0;
    }
    在这里,实际上是通过PrintMessage()函数定义了一个通用算法框架:首先打印页眉;然后通过函数指针回调函数,打印具体的消息;最后打印页脚。PrintMessage()函数只完成最基本的页眉、页脚的打印,至于具体的消息,则留给回调函数负责,这就像留下一个插口,等待某个具体的回调函数插头的插入。在主函数中,通过给PrintMessage()函数传递不同的打印函数的指针,就如同将某个插头插入PrintMessage()函数所留下的插口中。不同函数指针插头的插入,可以改变PrintMessage()函数中的回调函数,进而改变PrintMessage()函数的行为,以达到对其行为进行自定义的效果。

    C/C++学习交流L:点击进入

    e5ecd3c718c7b2b316949a5f8195091e.png


    clip_image002
    通过回调函数改变一个函数的行为、对其行为进行自定义的特性被广泛应用在通用算法的设计中。假设有这样一种情况,要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序等,为了使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现具体的排序逻辑,或者让库用于多种数据类型。这时,回调函数就可以派上用场了。可以在通用算法中定义好算法的框架,至于其中核心的算法逻辑,则留待回调函数去完成。用户可以通过不同的回调函数,轻松简单地实现各种算法,对算法进行自定义。

    想要学习更多C/C++编程知识与分享,记得点击收藏与关注哦!

    展开全文
  • 简单描述一下什么是回调函数:  用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。如果函数可以再不同的时间执行不同类型的工作或者执行只能由函数调用者定义的工作,都可以使用回调函数。回...

    今天看到回调函数,有点迷糊,找了好多搜索引擎的资料,都不是让我很能理解,看了《c和指针》我才明白了。

    简单描述一下什么是回调函数:

      用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。如果函数可以再不同的时间执行不同类型的工作或者执行只能由函数调用者定义的工作,都可以使用回调函数。 回调函数无法知道比较的值的类型,所以参数的类型被声明为void*。表示一个指向未知类型的指针。 可以通过函数指针来实现回调函数。一个指向回调函数的指针作为参数传递给另一个函数,后者使用这个指针调用回调函数。 

      可能说了太多定义也不会很是明白,来几个事例说说。

      当我们在在链表中查找一个数时,我们一般会这样写:

    复制代码

     1 Node *search_list( Node *node, int const value )
     2 {
     3     while ( NULL != node ){
     4         if ( node->value == value ){
     5             break;
     6         }
     7         node = node->link;
     8     }
     9 
    10     return node;
    11 }

    复制代码

    这样就限制我们只能在查找的数必须是int类型,当变为其他类型时我们就无法用这个函数,但是重新写一个函数,他们重复代码又太多。那我们看看用回调函数如何办到。  

    回调函数查找:

     

    复制代码

    1 int compare_int( void const *a, void const *b )
    2 {
    3     if ( *( int * )a == *( int * )b ){
    4         return 0;
    5     }
    6 
    7     return 1;
    8 }

    复制代码

     

    复制代码

     1 Node *search_list(Node *node, void const *value, 
     2     int (*compare)(void const *, void const *))  //函数指针
     3 {
     4     while(node != NULL){
     5         if(compare(&node->value, value) == 0)  //相等
     6             break;
     7         node = node->link;
     8     }
     9     return node;
    10 }

    复制代码

     

     这样利用回调函数就可以解决如上问题。我们把一个函数指针( int (*compare)(void const *, void const *) )作为参数传递给查找函数,查找函数将“回调”比较函数。当我们需要执行不同类型的比较时我们合理调用该函数。例如:当我们整形查找时: search_list( root, &desired_value, compare_int ); ,使用字符查找时: search_list( root, &desired_value, compare_char ); 。这就是回调函数简单的应用,当然回调函数不仅仅只是用于这些简单的例子,比如库函数qsort就是利用回调函数实现。

      函数原型如下:

    复制代码

    void qsort(
       void *base,    //字符串首地址
       size_t num,  //排序总个数
       size_t width, //排序元素的大小
       int (__cdecl *compare )(const void *, const void *)  //函数指针
    );

    复制代码

      库函数实现:

    复制代码

    
     
    void qsort(
       void *base,    //字符串首地址
       size_t num,  //排序总个数
       size_t width, //排序元素的大小
       int (__cdecl *compare )(const void *, const void *)  //函数指针
    );
    {
        char *lo, *hi;              /* ends of sub-array currently sorting */
        char *mid;                  /* points to middle of subarray */
        char *loguy, *higuy;        /* traveling pointers for partition step */
        size_t size;                /* size of the sub-array */
        char *lostk[STKSIZ], *histk[STKSIZ];
        int stkptr;                 /* stack for saving sub-array to be processed */
    
        /* validation section */
        _VALIDATE_RETURN_VOID(base != NULL || num == 0, EINVAL);
        _VALIDATE_RETURN_VOID(width > 0, EINVAL);
        _VALIDATE_RETURN_VOID(comp != NULL, EINVAL);
    
        if (num < 2)
            return;                 /* nothing to do */
    
        stkptr = 0;                 /* initialize stack */
    
        lo = (char *)base;
        hi = (char *)base + width * (num-1);        /* initialize limits */
    
        /* this entry point is for pseudo-recursion calling: setting
           lo and hi and jumping to here is like recursion, but stkptr is
           preserved, locals aren't, so we preserve stuff on the stack */
    recurse:
    
        size = (hi - lo) / width + 1;        /* number of el's to sort */
    
        /* below a certain size, it is faster to use a O(n^2) sorting method */
        if (size <= CUTOFF) {
            __SHORTSORT(lo, hi, width, comp, context);
        }
        else {
            /* First we pick a partitioning element.  The efficiency of the
               algorithm demands that we find one that is approximately the median
               of the values, but also that we select one fast.  We choose the
               median of the first, middle, and last elements, to avoid bad
               performance in the face of already sorted data, or data that is made
               up of multiple sorted runs appended together.  Testing shows that a
               median-of-three algorithm provides better performance than simply
               picking the middle element for the latter case. */
    
            mid = lo + (size / 2) * width;      /* find middle element */
    
            /* Sort the first, middle, last elements into order */
            if (__COMPARE(context, lo, mid) > 0) {
                swap(lo, mid, width);
            }
            if (__COMPARE(context, lo, hi) > 0) {
                swap(lo, hi, width);
            }
            if (__COMPARE(context, mid, hi) > 0) {
                swap(mid, hi, width);
            }
    
            /* We now wish to partition the array into three pieces, one consisting
               of elements <= partition element, one of elements equal to the
               partition element, and one of elements > than it.  This is done
               below; comments indicate conditions established at every step. */
    
            loguy = lo;
            higuy = hi;
    
            /* Note that higuy decreases and loguy increases on every iteration,
               so loop must terminate. */
            for (;;) {
                /* lo <= loguy < hi, lo < higuy <= hi,
                   A[i] <= A[mid] for lo <= i <= loguy,
                   A[i] > A[mid] for higuy <= i < hi,
                   A[hi] >= A[mid] */
    
                /* The doubled loop is to avoid calling comp(mid,mid), since some
                   existing comparison funcs don't work when passed the same
                   value for both pointers. */
    
                if (mid > loguy) {
                    do  {
                        loguy += width;
                    } while (loguy < mid && __COMPARE(context, loguy, mid) <= 0);
                }
                if (mid <= loguy) {
                    do  {
                        loguy += width;
                    } while (loguy <= hi && __COMPARE(context, loguy, mid) <= 0);
                }
    
                /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
                   either loguy > hi or A[loguy] > A[mid] */
    
                do  {
                    higuy -= width;
                } while (higuy > mid && __COMPARE(context, higuy, mid) > 0);
    
                /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi,
                   either higuy == lo or A[higuy] <= A[mid] */
    
                if (higuy < loguy)
                    break;
    
                /* if loguy > hi or higuy == lo, then we would have exited, so
                   A[loguy] > A[mid], A[higuy] <= A[mid],
                   loguy <= hi, higuy > lo */
    
                swap(loguy, higuy, width);
    
                /* If the partition element was moved, follow it.  Only need
                   to check for mid == higuy, since before the swap,
                   A[loguy] > A[mid] implies loguy != mid. */
    
                if (mid == higuy)
                    mid = loguy;
    
                /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
                   of loop is re-established */
            }
    
            /*     A[i] <= A[mid] for lo <= i < loguy,
                   A[i] > A[mid] for higuy < i < hi,
                   A[hi] >= A[mid]
                   higuy < loguy
               implying:
                   higuy == loguy-1
                   or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */
    
            /* Find adjacent elements equal to the partition element.  The
               doubled loop is to avoid calling comp(mid,mid), since some
               existing comparison funcs don't work when passed the same value
               for both pointers. */
    
            higuy += width;
            if (mid < higuy) {
                do  {
                    higuy -= width;
                } while (higuy > mid && __COMPARE(context, higuy, mid) == 0);
            }
            if (mid >= higuy) {
                do  {
                    higuy -= width;
                } while (higuy > lo && __COMPARE(context, higuy, mid) == 0);
            }
    
            /* OK, now we have the following:
                  higuy < loguy
                  lo <= higuy <= hi
                  A[i]  <= A[mid] for lo <= i <= higuy
                  A[i]  == A[mid] for higuy < i < loguy
                  A[i]  >  A[mid] for loguy <= i < hi
                  A[hi] >= A[mid] */
    
            /* We've finished the partition, now we want to sort the subarrays
               [lo, higuy] and [loguy, hi].
               We do the smaller one first to minimize stack usage.
               We only sort arrays of length 2 or more.*/
    
            if ( higuy - lo >= hi - loguy ) {
                if (lo < higuy) {
                    lostk[stkptr] = lo;
                    histk[stkptr] = higuy;
                    ++stkptr;
                }                           /* save big recursion for later */
    
                if (loguy < hi) {
                    lo = loguy;
                    goto recurse;           /* do small recursion */
                }
            }
            else {
                if (loguy < hi) {
                    lostk[stkptr] = loguy;
                    histk[stkptr] = hi;
                    ++stkptr;               /* save big recursion for later */
                }
    
                if (lo < higuy) {
                    hi = higuy;
                    goto recurse;           /* do small recursion */
                }
            }
        }
    
        /* We have sorted the array, except for any pending sorts on the stack.
           Check if there are any, and do them. */
    
        --stkptr;
        if (stkptr >= 0) {
            lo = lostk[stkptr];
            hi = histk[stkptr];
            goto recurse;           /* pop subarray from stack */
        }
        else
            return;                 /* all subarrays done */
    }

    复制代码

      为了更好地理解回调函数,接下来我们来写一个自己的qsort函数(利用冒泡排序)

     

    复制代码

    int char_compare(void const * c1,void const* c2) //比较函数
    {
        int a = *((int*)c1);
        int b = *((int*)c2);
        return a>b ? 1 : a<b ? -1 : 0;
    }
    
    void Swap(char *str1,char *str2,int size) 
    {
        while (size--)
        {
            char tmp = *str1;
            *str1 = *str2;
            *str2 = tmp;
            str1++;str2++;
        }
    }
    void MyQsort(void *str,int len,int elen,int(*compare)(void const*,void const*))  //基于回调函数写的排序算法
    {
        int i = 0;
        int j = 0;
        int flag = 1;
        for (i=0; i<len-1; i++)
        {
            for (j=0; j<len-1-i; j++)
            {
                if (compare((char*)str+j*elen,(char*)str+(j+1)*elen)>0)
                {
                    flag = 0;
                    Swap((char*)str+j*elen,(char*)str+(j+1)*elen,elen);
                }
            }
            if (flag)
                return;
        }
    }

    复制代码

     

    看了例题在来说说原理

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

    回调函数实现的机制是:

      (1)定义一个回调函数;

      (2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;

      (3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

     

    看了两个例子大家应该能理解回调函数了,如果还有什么问题可以私信我,建议把指针这节理解透彻,这是指针的

    参考文献:

    Kenneth A.Reek 著  徐波 译.c和指针.人民邮电出版社.2008

    展开全文
  • 简单描述一下什么是回调函数:  用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。如果函数可以再不同的时间执行不同类型的工作或者执行只能由函数调用者定义的工作,都可以使用回调函数。回...

      今天看到回调函数,有点迷糊,找了好多搜索引擎的资料,都不是让我很能理解,看了《c和指针》我才明白了。

    简单描述一下什么是回调函数:

      用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。如果函数可以再不同的时间执行不同类型的工作或者执行只能由函数调用者定义的工作,都可以使用回调函数。 回调函数无法知道比较的值的类型,所以参数的类型被声明为void*。表示一个指向未知类型的指针。 可以通过函数指针来实现回调函数。一个指向回调函数的指针作为参数传递给另一个函数,后者使用这个指针调用回调函数。 

      可能说了太多定义也不会很是明白,来几个事例说说。

      当我们在在链表中查找一个数时,我们一般会这样写:

     1 Node *search_list( Node *node, int const value )
     2 {
     3     while ( NULL != node ){
     4         if ( node->value == value ){
     5             break;
     6         }
     7         node = node->link;
     8     }
     9 
    10     return node;
    11 }

    这样就限制我们只能在查找的数必须是int类型,当变为其他类型时我们就无法用这个函数,但是重新写一个函数,他们重复代码又太多。那我们看看用回调函数如何办到。  

    回调函数查找:

     

    1 int compare_int( void const *a, void const *b )
    2 {
    3     if ( *( int * )a == *( int * )b ){
    4         return 0;
    5     }
    6 
    7     return 1;
    8 }

     

     1 Node *search_list(Node *node, void const *value, 
     2     int (*compare)(void const *, void const *))  //函数指针
     3 {
     4     while(node != NULL){
     5         if(compare(&node->value, value) == 0)  //相等
     6             break;
     7         node = node->link;
     8     }
     9     return node;
    10 }

     

     这样利用回调函数就可以解决如上问题。我们把一个函数指针( int (*compare)(void const *, void const *) )作为参数传递给查找函数,查找函数将“回调”比较函数。当我们需要执行不同类型的比较时我们合理调用该函数。例如:当我们整形查找时: search_list( root, &desired_value, compare_int ); ,使用字符查找时: search_list( root, &desired_value, compare_char ); 。这就是回调函数简单的应用,当然回调函数不仅仅只是用于这些简单的例子,比如库函数qsort就是利用回调函数实现。

      函数原型如下:

    void qsort(
       void *base,    //字符串首地址
       size_t num,  //排序总个数
       size_t width, //排序元素的大小
       int (__cdecl *compare )(const void *, const void *)  //函数指针
    );

      库函数实现:

    
    
    void qsort(
       void *base,    //字符串首地址
       size_t num,  //排序总个数
       size_t width, //排序元素的大小
       int (__cdecl *compare )(const void *, const void *)  //函数指针
    );
    {
        char *lo, *hi;              /* ends of sub-array currently sorting */
        char *mid;                  /* points to middle of subarray */
        char *loguy, *higuy;        /* traveling pointers for partition step */
        size_t size;                /* size of the sub-array */
        char *lostk[STKSIZ], *histk[STKSIZ];
        int stkptr;                 /* stack for saving sub-array to be processed */
    
        /* validation section */
        _VALIDATE_RETURN_VOID(base != NULL || num == 0, EINVAL);
        _VALIDATE_RETURN_VOID(width > 0, EINVAL);
        _VALIDATE_RETURN_VOID(comp != NULL, EINVAL);
    
        if (num < 2)
            return;                 /* nothing to do */
    
        stkptr = 0;                 /* initialize stack */
    
        lo = (char *)base;
        hi = (char *)base + width * (num-1);        /* initialize limits */
    
        /* this entry point is for pseudo-recursion calling: setting
           lo and hi and jumping to here is like recursion, but stkptr is
           preserved, locals aren't, so we preserve stuff on the stack */
    recurse:
    
        size = (hi - lo) / width + 1;        /* number of el's to sort */
    
        /* below a certain size, it is faster to use a O(n^2) sorting method */
        if (size <= CUTOFF) {
            __SHORTSORT(lo, hi, width, comp, context);
        }
        else {
            /* First we pick a partitioning element.  The efficiency of the
               algorithm demands that we find one that is approximately the median
               of the values, but also that we select one fast.  We choose the
               median of the first, middle, and last elements, to avoid bad
               performance in the face of already sorted data, or data that is made
               up of multiple sorted runs appended together.  Testing shows that a
               median-of-three algorithm provides better performance than simply
               picking the middle element for the latter case. */
    
            mid = lo + (size / 2) * width;      /* find middle element */
    
            /* Sort the first, middle, last elements into order */
            if (__COMPARE(context, lo, mid) > 0) {
                swap(lo, mid, width);
            }
            if (__COMPARE(context, lo, hi) > 0) {
                swap(lo, hi, width);
            }
            if (__COMPARE(context, mid, hi) > 0) {
                swap(mid, hi, width);
            }
    
            /* We now wish to partition the array into three pieces, one consisting
               of elements <= partition element, one of elements equal to the
               partition element, and one of elements > than it.  This is done
               below; comments indicate conditions established at every step. */
    
            loguy = lo;
            higuy = hi;
    
            /* Note that higuy decreases and loguy increases on every iteration,
               so loop must terminate. */
            for (;;) {
                /* lo <= loguy < hi, lo < higuy <= hi,
                   A[i] <= A[mid] for lo <= i <= loguy,
                   A[i] > A[mid] for higuy <= i < hi,
                   A[hi] >= A[mid] */
    
                /* The doubled loop is to avoid calling comp(mid,mid), since some
                   existing comparison funcs don't work when passed the same
                   value for both pointers. */
    
                if (mid > loguy) {
                    do  {
                        loguy += width;
                    } while (loguy < mid && __COMPARE(context, loguy, mid) <= 0);
                }
                if (mid <= loguy) {
                    do  {
                        loguy += width;
                    } while (loguy <= hi && __COMPARE(context, loguy, mid) <= 0);
                }
    
                /* lo < loguy <= hi+1, A[i] <= A[mid] for lo <= i < loguy,
                   either loguy > hi or A[loguy] > A[mid] */
    
                do  {
                    higuy -= width;
                } while (higuy > mid && __COMPARE(context, higuy, mid) > 0);
    
                /* lo <= higuy < hi, A[i] > A[mid] for higuy < i < hi,
                   either higuy == lo or A[higuy] <= A[mid] */
    
                if (higuy < loguy)
                    break;
    
                /* if loguy > hi or higuy == lo, then we would have exited, so
                   A[loguy] > A[mid], A[higuy] <= A[mid],
                   loguy <= hi, higuy > lo */
    
                swap(loguy, higuy, width);
    
                /* If the partition element was moved, follow it.  Only need
                   to check for mid == higuy, since before the swap,
                   A[loguy] > A[mid] implies loguy != mid. */
    
                if (mid == higuy)
                    mid = loguy;
    
                /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
                   of loop is re-established */
            }
    
            /*     A[i] <= A[mid] for lo <= i < loguy,
                   A[i] > A[mid] for higuy < i < hi,
                   A[hi] >= A[mid]
                   higuy < loguy
               implying:
                   higuy == loguy-1
                   or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */
    
            /* Find adjacent elements equal to the partition element.  The
               doubled loop is to avoid calling comp(mid,mid), since some
               existing comparison funcs don't work when passed the same value
               for both pointers. */
    
            higuy += width;
            if (mid < higuy) {
                do  {
                    higuy -= width;
                } while (higuy > mid && __COMPARE(context, higuy, mid) == 0);
            }
            if (mid >= higuy) {
                do  {
                    higuy -= width;
                } while (higuy > lo && __COMPARE(context, higuy, mid) == 0);
            }
    
            /* OK, now we have the following:
                  higuy < loguy
                  lo <= higuy <= hi
                  A[i]  <= A[mid] for lo <= i <= higuy
                  A[i]  == A[mid] for higuy < i < loguy
                  A[i]  >  A[mid] for loguy <= i < hi
                  A[hi] >= A[mid] */
    
            /* We've finished the partition, now we want to sort the subarrays
               [lo, higuy] and [loguy, hi].
               We do the smaller one first to minimize stack usage.
               We only sort arrays of length 2 or more.*/
    
            if ( higuy - lo >= hi - loguy ) {
                if (lo < higuy) {
                    lostk[stkptr] = lo;
                    histk[stkptr] = higuy;
                    ++stkptr;
                }                           /* save big recursion for later */
    
                if (loguy < hi) {
                    lo = loguy;
                    goto recurse;           /* do small recursion */
                }
            }
            else {
                if (loguy < hi) {
                    lostk[stkptr] = loguy;
                    histk[stkptr] = hi;
                    ++stkptr;               /* save big recursion for later */
                }
    
                if (lo < higuy) {
                    hi = higuy;
                    goto recurse;           /* do small recursion */
                }
            }
        }
    
        /* We have sorted the array, except for any pending sorts on the stack.
           Check if there are any, and do them. */
    
        --stkptr;
        if (stkptr >= 0) {
            lo = lostk[stkptr];
            hi = histk[stkptr];
            goto recurse;           /* pop subarray from stack */
        }
        else
            return;                 /* all subarrays done */
    }

      为了更好地理解回调函数,接下来我们来写一个自己的qsort函数(利用冒泡排序)

     

    int char_compare(void const * c1,void const* c2) //比较函数
    {
        int a = *((int*)c1);
        int b = *((int*)c2);
        return a>b ? 1 : a<b ? -1 : 0;
    }
    
    void Swap(char *str1,char *str2,int size) 
    {
        while (size--)
        {
            char tmp = *str1;
            *str1 = *str2;
            *str2 = tmp;
            str1++;str2++;
        }
    }
    void MyQsort(void *str,int len,int elen,int(*compare)(void const*,void const*))  //基于回调函数写的排序算法
    {
        int i = 0;
        int j = 0;
        int flag = 1;
        for (i=0; i<len-1; i++)
        {
            for (j=0; j<len-1-i; j++)
            {
                if (compare((char*)str+j*elen,(char*)str+(j+1)*elen)>0)
                {
                    flag = 0;
                    Swap((char*)str+j*elen,(char*)str+(j+1)*elen,elen);
                }
            }
            if (flag)
                return;
        }
    }

     

    看了例题在来说说原理

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

    回调函数实现的机制是:

      (1)定义一个回调函数;

      (2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;

      (3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

     

    看了两个例子大家应该能理解回调函数了,如果还有什么问题可以私信我,建议把指针这节理解透彻,这是指针的

    参考文献:

    Kenneth A.Reek 著  徐波 译.c和指针.人民邮电出版社.2008

     

     

    转载于:https://www.cnblogs.com/zhonglongbo/p/8410464.html

    展开全文
  • C++ 回调函数

    2021-01-03 14:08:44
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • 什么是回调函数

    2008-04-10 16:57:00
    一,回调函数 我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数...
  • 回调函数

    2014-07-20 20:01:20
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • C回调函数

    千次阅读 2019-05-21 19:03:14
    如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,...
  • C++回调函数详解

    2015-07-31 13:50:43
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • 什么是回调函数

    2008-11-19 22:26:00
    一,回调函数我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)...
  • 关于回调函数

    2016-11-25 21:58:42
    一,回调函数我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)...
  • 回调函数

    2011-01-26 10:24:00
    <br />我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)...
  • 回调函数简介

    2015-05-04 17:22:02
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • 回调函数讲解

    2015-01-22 12:36:07
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • 回调函数详解

    2014-01-07 12:31:58
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • C++回调函数

    2014-03-14 18:24:12
     我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢...
  • C++回调函数用法

    千次阅读 2017-09-08 10:19:50
    我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?...
  • js的回调函数详解

    2020-12-09 15:15:31
    现在做native App 和Web App是主流,也就是说...我们来看一下一个粗略的一个定义函数a有一个参数,这个参数是函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。”,这句话的意思是函数b以一个参数的形

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 211
精华内容 84
关键字:

参数是回调函数怎么定义