打印
[应用相关]

C 语言回调函数详解

[复制链接]
2884|35
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
1. 什么是回调函数?
回调函数,光听名字就比普通函数要高大上一些,那到底什么是回调函数呢?恕我读得书少,没有在那本书上看到关于回调函数的定义。我在百度上搜了一下,发现众说纷纭,有很大一部分都是使用类似这么一个场景来说明:A君去B君店里买东西,恰好缺货,A君留下号码给B君,有货时通知A君。感觉这个让人更容易想到的是异步操作,而不是回调。另外还有两句英文让我印象深刻:1) If you call me, I will call you back; 2) Don't call me, I will call you. 看起来好像很有道理,但是仔细一想,普通函数不也可以做到这两点吗?所以,我觉得这样的说法都不是很妥当,因为我觉得这些说法都没有把回调函数的特点表达出来,也就是都看不到和普通函数到底有什么差别。不过,百度百科的解析我觉得还算不错(虽然经常吐槽百度搜索...):回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
下面先说说我的看法。我们可以先在字面上先做个分解,对于"回调函数",中文其实可以理解为这么两种意思:1) 被回调的函数;2) 回头执行调用动作的函数。那这个回头调用又是什么鬼?
先来看看来自维基百科的对回调(Callback)的解析:In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback. 也就是说,把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称之为异步回调。关于同步和异步,这里不作讨论,请查阅相关资料。
再来看看来自Stack Overflow某位大神简洁明了的表述:A "callback" is any function that is called by another function which takes the first function as a parameter。 也就是说,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。到此应该明白回调函数的定义了吧?

使用特权

评论回复
评论
wwchang 2020-3-4 10:51 回复TA
写得很好 
cooldog123pp 2020-2-26 14:50 回复TA
认真看完了,多谢楼主,受教了。 
沙发
gejigeji521|  楼主 | 2020-2-24 13:42 | 只看该作者
2. 为什么要使用回调函数?
很多朋友可能会想,为什么不像普通函数调用那样,在回调的地方直接写函数的名字呢?这样不也可以吗?为什么非得用回调函数呢?有这个想法很好,因为在网上看到解析回调函数的很多例子,其实完全可以用普通函数调用来实现的。要回答这个问题,我们先来了解一下回到函数的好处和作用,那就是解耦,对,就是这么简单的答案,就是因为这个特点,普通函数代替不了回调函数。所以,在我眼里,这才是回调函数最大的特点。来看看维基百科上面我觉得画得很好的一张图片。
下面以一段不完整的 C 语言代码来呈现上图的意思:
#include<stdio.h>
#include<softwareLib.h> // 包含Library Function所在读得Software library库的头文件

int Callback() // Callback Function
{
    // TODO
    return 0;
}
int main() // Main program
{
    // TODO
    Library(Callback);
    // TODO
    return 0;
}

使用特权

评论回复
板凳
gejigeji521|  楼主 | 2020-2-24 13:43 | 只看该作者
乍一看,回调似乎只是函数间的调用,和普通函数调用没啥区别,但仔细一看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且丝毫不需要修改库函数的实现,这就是解耦。再仔细看看,主函数和回调函数是在同一层的,而库函数在另外一层,想一想,如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。现在再把main()、Library()和Callback()函数套回前面 F1、F2和F3函数里面,是不是就更明白了?

明白了回调函数的特点,是不是也可以大概知道它应该在什么情况下使用了?没错,你可以在很多地方使用回调函数来代替普通的函数调用,但是在我看来,如果需要降低耦合度的时候,更应该使用回调函数。

使用特权

评论回复
评论
zyj9490 2020-2-26 21:13 回复TA
通过回调,表现出事件的输出或叫发生。 
地板
gejigeji521|  楼主 | 2020-2-24 13:43 | 只看该作者
3. 怎么使用回调函数?
知道了什么是回调函数,了解了回调函数的特点,那么应该怎么使用回调函数?下面来看一段简单的可以执行的同步回调函数代码。
#include<stdio.h>

int Callback_1() // Callback Function 1
{
    printf("Hello, this is Callback_1 \n");
    return 0;
}

int Callback_2() // Callback Function 2
{
    printf("Hello, this is Callback_2 \n");
    return 0;
}

int Callback_3() // Callback Function 3
{
    printf("Hello, this is Callback_3 \n");
    return 0;
}

int Handle(int (*Callback)())
{
    printf("Entering Handle Function.\n ");
    Callback();
    printf("Leaving Handle Function.\n ");
}

int main()
{
    printf("Entering Main Function.\n ");
    Handle(Callback_1);
    Handle(Callback_2);
    Handle(Callback_3);
    printf("Leaving Main Function.\n");
    return 0;
}

运行结果:Entering Main Function.
Entering Handle Function.
Hello, this is Callback_1
Leaving Handle Function.
Entering Handle Function.
Hello, this is Callback_2
Leaving Handle Function.
Entering Handle Function.
Hello, this is Callback_3
Leaving Handle Function.
Leaving Main Function.

使用特权

评论回复
5
gejigeji521|  楼主 | 2020-2-24 13:44 | 只看该作者
可以看到,Handle()函数里面的参数是一个指针,在main()函数里调用Handle()函数的时候,给它传入了函数Callback_1()/Callback_2()/Callback_3()的函数名,这时候的函数名就是对应函数的指针,也就是说,回调函数其实就是函数指针的一种用法。现在再读一遍这句话:A "callback" is any function that is called by another function which takes the first function as a parameter,是不是就更明白了呢?

使用特权

评论回复
6
gejigeji521|  楼主 | 2020-2-24 13:46 | 只看该作者
4. 怎么使用带参数的回调函数?
眼尖的朋友可能发现了,前面的例子里面回调函数是没有参数的,那么我们能不能回调那些带参数的函数呢?答案是肯定的。那么怎么调用呢?我们稍微修改一下上面的例子就可以了:
#include<stdio.h>

int Callback_1(int x) // Callback Function 1
{
    printf("Hello, this is Callback_1: x = %d ", x);
    return 0;
}

int Callback_2(int x) // Callback Function 2
{
    printf("Hello, this is Callback_2: x = %d ", x);
    return 0;
}

int Callback_3(int x) // Callback Function 3
{
    printf("Hello, this is Callback_3: x = %d ", x);
    return 0;
}

int Handle(int y, int (*Callback)(int))
{
    printf("Entering Handle Function. ");
    Callback(y);
    printf("Leaving Handle Function. ");
}

int main()
{
    int a = 2;
    int b = 4;
    int c = 6;
    printf("Entering Main Function. ");
    Handle(a, Callback_1);
    Handle(b, Callback_2);
    Handle(c, Callback_3);
    printf("Leaving Main Function. ");
    return 0;
}
运行结果:
Entering Main Function.Entering Handle Function.Hello, this is Callback_1: x = 2Leaving Handle Function.Entering Handle Function.Hello, this is Callback_2: x = 4Leaving Handle Function.Entering Handle Function.Hello, this is Callback_3: x = 6Leaving Handle Function.Leaving Main Function.

使用特权

评论回复
7
gejigeji521|  楼主 | 2020-2-24 13:48 | 只看该作者
可以看到,并不是直接把int Handle(int (*Callback)()) 改成 int Handle(int (*Callback)(int)) 就可以的,而是通过另外增加一个参数来保存回调函数的参数值,像这里 int Handle(int y, int (*Callback)(int)) 的参数 y。同理,可以使用多个参数的回调函数。

使用特权

评论回复
8
llb560| | 2020-2-24 17:52 | 只看该作者
感谢楼主的讲解,通过例子讲解非常直观清楚。

使用特权

评论回复
9
zyx818102| | 2020-2-26 15:28 | 只看该作者
非常感谢楼主的分享,受教了!一致没深入理解回调函数意义,从头认真读完。受益匪浅

使用特权

评论回复
10
Sunriver_Yao| | 2020-2-26 16:02 | 只看该作者
NB!  清晰!

使用特权

评论回复
11
gejigeji521|  楼主 | 2020-2-26 17:52 | 只看该作者
我知道,这个给我盖章的小编一定是个高手,竟然看出来这个贴的含金量了。

使用特权

评论回复
12
toofree| | 2020-2-27 12:28 | 只看该作者
通俗易懂,简洁明了,直观形象

使用特权

评论回复
13
gejigeji521|  楼主 | 2020-3-3 14:20 | 只看该作者
其实我是从其他地方看到的。主要ST的库函数总是用回调,搞晕我了。现在我搞懂了,也分享过来。

使用特权

评论回复
14
zlutian| | 2020-3-4 22:34 | 只看该作者
深入浅出,谢谢!

使用特权

评论回复
15
lihuasoft| | 2020-3-4 22:55 | 只看该作者
一样的曾经困惑,如今的懂了却说不出口

使用特权

评论回复
16
yiyigirl2014| | 2020-3-6 21:15 | 只看该作者
太赞了。

使用特权

评论回复
17
gaoyang9992006| | 2020-3-17 19:22 | 只看该作者
非常棒的帖子。我已经把这帖子内容作为今晚讲课的一部分。

使用特权

评论回复
18
hanzhen654| | 2020-3-17 20:31 | 只看该作者
有个困惑?回调函数和注册函数什么关系?

使用特权

评论回复
19
gejigeji521|  楼主 | 2020-3-19 10:32 | 只看该作者
hanzhen654 发表于 2020-3-17 20:31
有个困惑?回调函数和注册函数什么关系?

当程序中,某个功能不定时,就用函数指针代表该功能,使用时,需要函数实现什么功能,则将需要实现功能的函数赋值给函数指针,则函数指针出现的地方就能按挂接的回调函数(需要的功能函数)执行,而挂接赋值的过程可以用注册函数来实现。

  函数指针通常用全局变量来指定。

  例如有max(a,b),min(a,b), 开始时不定用哪个函数,则可以用函数指针全局变量unsigned int* g_func(a,b)来代替,

  如:

Int (* g_func)(int a, int b) = NULL;

Int max(int a,int b);

Int min(int a,int b);



      main()

     {

        g_func = max;

        g_func(a,b);  /*相当于执行max(a,b)*/

     }

Max和min两个函数称为钩子函数或回调函数;

在写main函数时不知道要完成什么功能 ,则用函数指针作为接口,可以挂上不同的函数完成不同的功能;

注册函数:将钩子函数挂在已声明的全局变量函数指针上;注册函数的入参为函数指针

Install(int (*p)())

{

  p = max;

}

Install(g_func)即代表g_func = max;

使用特权

评论回复
20
gejigeji521|  楼主 | 2020-3-19 10:33 | 只看该作者
回调函数就是个函数,只不过调用它是通过指针。所以要有函数的指针,就是函数名。注册函数,是用户关联函数指针与回调函数的。
由于可以直接将函数名作为指针传递给主调函数,所以注册函数可以有,也可以没有,就看你如何实现具体的调用细节。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

177

主题

2229

帖子

8

粉丝