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 就是回调函数。到此应该明白回调函数的定义了吧?

2. 为什么要使用回调函数?

很多朋友可能会想,为什么不像普通函数调用那样,在回调的地方直接写函数的名字呢?这样不也可以吗?为什么非得用回调函数呢?有这个想法很好,因为在网上看到解析回调函数的很多例子,其实完全可以用普通函数调用来实现的。要回答这个问题,我们先来了解一下回到函数的好处和作用,那就是解耦,对,就是这么简单的答案,就是因为这个特点,普通函数代替不了回调函数。所以,在我眼里,这才是回调函数最大的特点。来看看维基百科上面我觉得画得很好的一张图片。

Callback

下面以一段不完整的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;

}

  

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

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

3. 怎么使用回调函数?

知道了什么是回调函数,了解了回调函数的特点,那么应该怎么使用回调函数?下面来看一段简单的可以执行的同步回调函数代码。

#include<stdio.h>

int Callback_1() // Callback Function 1

{

printf("Hello, this is Callback_1 ");

return ;

}

int Callback_2() // Callback Function 2

{

printf("Hello, this is Callback_2 ");

return ;

}

int Callback_3() // Callback Function 3

{

printf("Hello, this is Callback_3 ");

return ;

}

int Handle(int (*Callback)())

{

printf("Entering Handle Function. ");

Callback();

printf("Leaving Handle Function. ");

}

int main()

{

printf("Entering Main Function. ");

Handle(Callback_1);

Handle(Callback_2);

Handle(Callback_3);

printf("Leaving Main Function. ");

return ;

}

运行结果:

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.

可以看到,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,是不是就更明白了呢?

4. 怎么使用带参数的回调函数?

眼尖的朋友可能发现了,前面的例子里面回调函数是没有参数的,那么我们能不能回调那些带参数的函数呢?答案是肯定的。那么怎么调用呢?我们稍微修改一下上面的例子就可以了:

#include<stdio.h>

int Callback_1(int x) // Callback Function 1

{

printf("Hello, this is Callback_1: x = %d ", x);

return ;

}

int Callback_2(int x) // Callback Function 2

{

printf("Hello, this is Callback_2: x = %d ", x);

return ;

}

int Callback_3(int x) // Callback Function 3

{

printf("Hello, this is Callback_3: x = %d ", x);

return ;

}

int Handle(int y, int (*Callback)(int))

{

printf("Entering Handle Function. ");

Callback(y);

printf("Leaving Handle Function. ");

}

int main()

{

int a = ;

int b = ;

int c = ;

printf("Entering Main Function. ");

Handle(a, Callback_1);

Handle(b, Callback_2);

Handle(c, Callback_3);

printf("Leaving Main Function. ");

return ;

}

运行结果:

Entering Main Function.

Entering Handle Function.

Hello, this is Callback_1: x = 

Leaving Handle Function.

Entering Handle Function.

Hello, this is Callback_2: x = 

Leaving Handle Function.

Entering Handle Function.

Hello, this is Callback_3: x = 

Leaving Handle Function.

Leaving Main Function.

可以看到,并不是直接把int Handle(int (*Callback)()) 改成 int Handle(int (*Callback)(int)) 就可以的,而是通过另外增加一个参数来保存回调函数的参数值,像这里 int Handle(int y, int (*Callback)(int)) 的参数 y。同理,可以使用多个参数的回调函数。

C语言回调函数详解的更多相关文章

  1. C语言memset函数详解

    C语言memset函数详解 memset() 的作用:在一段内存块中填充某个给定的值,通常用于数组初始化与数组清零. 它是直接操作内存空间,mem即“内存”(memory)的意思.该函数的原型为: # ...

  2. js的回调函数详解

    本文主要介绍了个人对于javascript中回调函数的理解和使用方法及示例,需要的朋友可以参考下   现在做native App  和Web App是主流,也就是说现在各种基于浏览器的web app框 ...

  3. jQuery 源码解析(八) 异步队列模块 Callbacks 回调函数详解

    异步队列用于实现异步任务和回调函数的解耦,为ajax模块.队列模块.ready事件提供基础功能,包含三个部分:Query.Callbacks(flags).jQuery.Deferred(funct) ...

  4. java回调函数详解

    声明:博客参考于https://www.cnblogs.com/yangmin86/p/7090882.html,谢谢哥们 回调函数:是指在A类执行代码时,调用了B类中的方法,但B类中的方法执行了A类 ...

  5. C语言scanf函数详解

    函数名: scanf  功 能: 运行格式化输入  用 法: int scanf(char *format[,argument,...]); scanf()函数是通用终端格式化输入函数,它从标准输入设 ...

  6. memset函数及其用法,C语言memset函数详解

    在前面不止一次说过,定义变量时一定要进行初始化,尤其是数组和结构体这种占用内存大的数据结构.在使用数组的时候经常因为没有初始化而产生“烫烫烫烫烫烫”这样的野值,俗称“乱码”. 每种类型的变量都有各自的 ...

  7. c语言字符串函数详解

    转载请注明来源:https://www.cnblogs.com/hookjc/ oid *memset(void *dest, int c, size_t count); 将dest前面count个字 ...

  8. 【C语言】printf函数详解

    C语言printf函数详解 一.相关基础知识 请求printf()打印变量的指令取决于变量的类型,例如打印整数用%d符号,打印字符用%c符号,这些符号称为转换说明(conversion specifi ...

  9. 【转载】C语言itoa()函数和atoi()函数详解(整数转字符C实现)

    本文转自: C语言itoa()函数和atoi()函数详解(整数转字符C实现) 介绍 C语言提供了几个标准库函数,可以将任意类型(整型.长整型.浮点型等)的数字转换为字符串. int/float to ...

随机推荐

  1. 前端人脸识别框架Tracking.js与JqueryFaceDetection

    这篇文章主要就介绍两种前端的人脸识别框架(Tracking.js和JqueryFaceDetection) 技术特点 Tracking.js是使用js封装的一个框架,使用起来需要自己配置许多的东西,略 ...

  2. SpringCloud之eureka服务注册和服务发现

    服务注册中心 :eureka-server 作用:服务注册中心提供服务注册功能 服务提供方:eureka-client 作用:注册服务到服务注册中心 服务注册中心 :eureka-server 创建 ...

  3. NumPy副本和视图

    NumPy - 副本和视图 在执行函数时,其中一些返回输入数组的副本,而另一些返回视图. 当内容物理存储在另一个位置时,称为副本. 另一方面,如果提供了相同内存内容的不同视图,我们将其称为视图. 无复 ...

  4. 解读Mirantis最新的Neutron性能测试

    最近,mirantis的工程师发布了最新的基于Mitaka版本的Neutron性能测试结果.得出的结论是:Neutron现在的性能已经可以用生产环境了. 报告的三位作者都是OpenStack社区的活跃 ...

  5. Minhash 算法 及其应用

    背景: 我遇到一个问题,要计算140万商品的杰卡德相似度.如果直接要直接两两计算的话,这计算量根本算不了,而且也没必要. 分析: 在这些商品中很多商品的相似度并不高,也就是说其中达到相似度阈值的商品只 ...

  6. Softmax回归 softMax回归与logistic回归的关系

    简介 在本节中,我们介绍Softmax回归模型,该模型是logistic回归模型在多分类问题上的推广,在多分类问题中,类标签  可以取两个以上的值. Softmax回归模型对于诸如MNIST手写数字分 ...

  7. spring3: AOP 之代理机制

    Spring AOP通过代理模式实现,目前支持两种代理:JDK动态代理.CGLIB代理来创建AOP代理,Spring建议优先使用JDK动态代理. JDK动态代理:使用java.lang.reflect ...

  8. Selenium with Python 001 - 安装篇

    Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试. 通过Selenium Python的API,你可以非常直观 ...

  9. 用phpexcel插件导出excel2003

    ob_end_clean();//清空缓冲区并关闭输出缓冲(清除脏数据). header('Content-Type:application/vnd.ms-execel'); header('Cont ...

  10. Sturts2 三种开发模式 (转)

    1.实现与 Servlet API的交互struts2中提供了Map类型的request.session与application,可以从ActionContext对象中获得.ActionContext ...