函数回调过程和简单的matlab 求传递函数数指针过程有什么区别没?

回调函数(callback)是什么?
网上搜到的资源讲起来好凌乱
按投票排序
什么是回调函数?我们绕点远路来回答这个问题。编程分为两类:系统编程(system programming)和应用编程(application programming)。所谓系统编程,简单来说,就是编写库;而应用编程就是利用写好的各种库来编写具某种功用的程序,也就是应用。系统程序员会给自己写的库留下一些接口,即API(application programming interface,应用编程接口),以供应用程序员使用。所以在抽象层的图示里,库位于应用的底下。当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。如下图所示(图片来源:维基百科):可以看到,回调函数通常和应用处于同一抽象层(因为传入什么样的回调函数是在应用级别决定的)。而回调就成了一个高层调用底层,底层再回过头来调用高层的过程。(我认为)这应该是回调最早的应用之处,也是其得名如此的原因。回调机制的优势从上面的例子可以看出,回调机制提供了非常大的灵活性。请注意,从现在开始,我们把图中的库函数改称为中间函数了,这是因为回调并不仅仅用在应用和库之间。任何时候,只要想获得类似于上面情况的灵活性,都可以利用回调。这种灵活性是怎么实现的呢?乍看起来,回调似乎只是函数间的调用,但仔细一琢磨,可以发现两者之间的一个关键的不同:在回调中,我们利用某种方式,把回调函数像参数一样传入中间函数。可以这么理解,在传入一个回调函数之前,中间函数是不完整的。换句话说,程序可以在运行时,通过登记不同的回调函数,来决定、改变中间函数的行为。这就比简单的函数调用要灵活太多了。请看下面这段Python写成的回调的简单示例:`even.py`#回调函数1
#生成一个2k形式的偶数
def double(x):
return x * 2
#回调函数2
#生成一个4k形式的偶数
def quadruple(x):
return x * 4
`callback_demo.py`from even import *
#接受一个生成偶数的函数作为参数
#返回一个奇数
def getOddNumber(k, getEvenNumber):
return 1 + getEvenNumber(k)
#起始函数,这里是程序的主函数
def main():
#当需要生成一个2k+1形式的奇数时
i = getOddNumber(k, double)
#当需要一个4k+1形式的奇数时
i = getOddNumber(k, quadruple)
#当需要一个8k+1形式的奇数时
i = getOddNumber(k, lambda x: x * 8)
if __name__ == "__main__":
运行`callback_demp.py`,输出如下:3
上面的代码里,给`getOddNumber`传入不同的回调函数,它的表现也不同,这就是回调机制的优势所在。值得一提的是,上面的第三个回调函数是一个匿名函数。易被忽略的第三方通过上面的论述可知,中间函数和回调函数是回调的两个必要部分,不过人们往往忽略了回调里的第三位要角,就是中间函数的调用者。绝大多数情况下,这个调用者可以和程序的主函数等同起来,但为了表示区别,我这里把它称为起始函数(如上面的代码中注释所示)。之所以特意强调这个第三方,是因为我在网上读相关文章时得到一种印象,很多人把它简单地理解为两个个体之间的来回调用。譬如,很多中文网页在解释“回调”(callback)时,都会提到这么一句话:“If you call me, I will call you back.”我没有查到这句英文的出处。我个人揣测,很多人把起始函数和回调函数看作为一体,大概有两个原因:第一,可能是“回调”这一名字的误导;第二,给中间函数传入什么样的回调函数,是在起始函数里决定的。实际上,回调并不是“你我”两方的互动,而是ABC的三方联动。有了这个清楚的概念,在自己的代码里实现回调时才不容易混淆出错。另外,回调实际上有两种:阻塞式回调和延迟式回调。两者的区别在于:阻塞式回调里,回调函数的调用一定发生在起始函数返回之前;而延迟式回调里,回调函数的调用有可能是在起始函数返回之后。这里不打算对这两个概率做更深入的讨论,之所以把它们提出来,也是为了说明强调起始函数的重要性。网上的很多文章,提到这两个概念时,只是笼统地说阻塞式回调发生在主调函数返回之前,却没有明确这个主调函数到底是起始函数还是中间函数,不免让人糊涂,所以这里特意说明一下。另外还请注意,本文中所举的示例均为阻塞式回调。延迟式回调通常牵扯到多线程,我自己还没有完全搞明白,所以这里就不多说了。
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。回答完毕。
一般写程序是你调用系统的API,如果把关系反过来,你写一个函数,让系统调用你的函数,那就是回调了,那个被系统调用的函数就是回调函数。
回调函数就是你写好一个函数,让预先写好的系统来调用。你去调用系统的函数,是直调。让系统调用你的函数,就是回调。但假如满足于这种一句话结论,是不会真正明白的。回调函数可以看成,让别人做事,传进去的额外信息。比如,A 让 B 做事,根据粒度不同,可以理解成 A 函数调用 B 函数,或者 A 类使用 B 类,或者 A 组件使用 B 组件等等。反正就是 A 叫 B 做事。当 B 做这件事情的时候,自身的需要的信息不够,而 A 又有。就需要 A 从外面传进来,或者 B 做着做着主动向外面申请。对于 B 来说,一种被动得到信息,一种是主动去得到信息,有人给这两种方式术语,叫信息的 push,和信息的 pull。A 调用 B,A 需要向 B 传参数。如简单的函数:int max(int a, int b);
要使用这函数,得到两者最大的值, 外面就要传进来 a, b。这个很好理解。void qsort(void *, size_t, size_t, int (*)(const void *, const void *));
而这个函数用于排序,最后一个参数就是回调函数,似乎就比较难以理解了。这是因为人为割裂了代码和数据。我们暂停一下,看看计算机中比较诡异的地方,也就是代码(code)和数据(data)的统一。这是一个槛,如果不打通这个,很多概念就不清楚。我们常常说计算机程序分成 code 和 data 两部分。很多人就会觉得,code 是会运行的,是动的,data 是给 code 使用,是静态的,这是两种完全不同的东西。其实 code 只是对行为的一种描述,比如有个机器人可以开灯,关灯,扫地。如果跟机器人约定好,0 表示开灯,1 表示关灯,2 表示扫地。我发出指令串,0 1 2,就可以控制机器人开灯,关灯,扫地。再约定用二进制表示,两位一个指令,就有一个数字串,000111,这个时候 000111 这串数字就描述了机器人的一系列动作,这个就是从一方面理解是 code,它可以控制机器人的行为。但另一方面,它可以传递,可以记录,可以修改,也就是数据。只要大家都协商好,code 就可以编码成 data, 将 data 解释运行的时候,也变成了 code。code 和 data 可以不用区分,统一称为信息。既然 int max(int a, int b) 中 int,double 等表示普通 data 的东西可以传递进去,凭什么表示 code 的函数就不可以传进去了。有些语言确实是不区分的,它的 function(表示code)跟 int, double 的地位是一样的。这种语言就为函数是第一类值。而有些语言是不能存储函数,不能动态创建函数,不能动态销毁函数。只能存储一个指向函数的指针,这种语言称为函数是第二类值。另外有些语言不单可以传递函数,函数里面又用到一些外部信息(包括code, data)。那些语言可以将函数跟函数所用到的信息一起传递存储。这种将函数和它所用的信息作为一个整体,就为闭包。将代码和数据打通,统一起来。过了这个槛,很多难以理解的概念就会清晰很多。现在我们再回头看看回调函数。回调函数也就是是 A 让 B 做事,B 做着做着,信息不够,不知道怎么做了,就再让外面处理。比如上述排序例子,A 让 B 排序,B 会做排序,但排序需要知道哪个比哪个大,这点 B 自己不知道,就需要 A 告诉它。而这种判断那个大,本身是一种动作,既然 C 语言中不可以传进第一值的函数,就设计成传递第二值的函数指针,这个函数指针就是 A 传向 B 的信息,用来表示一个行为。这里本来 A 调用 B 的,结果 B 又调用了 A 告诉它的信息,也就叫 callback。再比如 A 让 B 监听系统的某个消息,比如敲了哪个键。跟着 B 监听到了,但它不知道怎么去处理这个消息,就给外面关心这个消息,又知道怎么去处理这个消息的人去处理,这个处理过程本身是个行为,既然这个语言不可以传递函数,又只能传一个函数指针了。假如我将函数指针存储下来,以后就可以随时调用。代码和数据都是信息,数据可以存储下来,用来表示行为的函数自然也可以存储下来。跟着有些人有会引申成,什么注册啊,通知啊等等等。假如 B 做监听,C, D, E, F, G, H 告诉 B 自己有兴趣知道这消息,那 B 监听到了就去告诉 C,D,E,F,G等人了,这样通知多人了,就叫广播。其实你理解了,根本不用去关心术语的。术语是别人要告诉你啊,或者你去告诉人啊,使用的一套约定的词语。本质上就这个东西,结果会有很多术语的。跟着再将回调的概念泛化,比如某人同时关心 A, B, C, D, E, F 事件,并且这些事件是一组的,比如敲键盘,鼠标移动,鼠标点击等一组。将一组事件结合起来。在有些语言就映射成接口,接口有 N 个函数。有些语言就映射成一个结构,里面放着 N 个函数指针。跟着就不是将单个函数指针传进去,而是将接口,或者函数指针的结构传进去。根据不同的用途,有些人叫它为代理,监听者,观察者等等。实际上也是将某种行为存储下来,以后有需要再进行调用。跟回调函数在更深层次是没有区别的。
回调方法介绍之中国好室友篇(Java示例)前言在Java社区的各种开源工具中,回调方法的使用俯拾即是。所以熟悉回调方法无疑能加速自己对开源轮子的掌握。网上搜了一些文章,奈何对回调方法的介绍大多只停留在什么是回调方法的程度上。本篇文章尝试从回调方法怎么来的、为什么要使用回调方法以及在实际项目中如何使用等方面来介绍下。场景场景选择的得当与否,很影响读者的继续阅读的兴趣甚至理解的主动性(长期作为互联网技术博文读者的我,深有感触)。好场景私以为是:熟悉且简单。本例小心翼翼选择的场景是:写作业。(hope you like)自己写注:写作业这个动作至少交代三个方面:谁,什么动作(写),写什么。下面先从(有个学生,写,作业)开始。# 1. 有个学生
Student student = new Student();
# 2. 该学生有写作业这个动作需要执行
student.doHomeWork(someHomeWork);
# 3. 注意到这个写作业这个动作是需要得到入参“作业”的后才能进行的。所以给这个学生new了个简单的题目做。
String aHomeWork = "1+1=?";
student.doHomeWork(aHomeWork);
至此,完成写作业的动作。完整代码public class Student {
public void doHomeWork(String homeWork) {
System.out.println("作业本");
if("1+1=?".equals(homeWork)) {
System.out.println("作业:"+homeWork+" 答案:"+"2");
System.out.println("作业:"+homeWork+" 答案:"+"不知道~~");
public static void main(String[] args) {
Student student = new Student();
String aHomeWork = "1+1=?";
student.doHomeWork(aHomeWork);
程序执行作业本
作业:1+1=? 答案:2
我们一定要把焦点聚焦到,”写作业“这个需求上面。该学生写作业的方法是现成的,但是需要有作业作为入参,怎么获取作业才是完成动作的关键。希望这点能深深印入我们的脑海。让室友帮忙解答上面的例子中该同学自己调用自己的方法,把收到的homework直接写了。但是现实可能会出现各种各样的问题导致该同学不能(xiang)自己来做。比如他想玩游戏或者有约会。所以他拜托了他的好室友(roommate)来帮忙写下。该怎么实现呢。#1. 因为室友帮忙写,所以在doHomeWork动作里面,就不需要有逻辑判断的代码,因为舍友会直接把答案写进来。改成:
student.doHomeWork(aHomeWork, theAnswer);
#上句中做作业的动作支持传入“作业”和“答案”,有了这两个,就说明能做好作业了。
#其中aHomeWork作业是已知的,但是theAnswer这个答案却是室友提供的。
#室友怎么才能提供答案呢,最简单是,室友这个对象直接提供一个传入作业,传出答案的方法,这样该同学就可以直接调用了。
RoomMate roomMate = new RoomMate();
String theAnswer = roomMate.getAnswer(aHomeWork);
student.doHomeWork(aHomeWork, theAnswer);
完整代码public class Student {
public void doHomeWork(String homeWork, String answer) {
System.out.println("作业本");
if(answer != null) {
System.out.println("作业:"+homeWork+" 答案:"+ answer);
System.out.println("作业:"+homeWork+" 答案:"+ "(空白)");
public static void main(String[] args) {
Student student = new Student();
String aHomeWork = "1+1=?";
RoomMate roomMate = new RoomMate();
String theAnswer = roomMate.getAnswer(aHomeWork);
student.doHomeWork(aHomeWork, theAnswer);
public class RoomMate {
public String getAnswer(String homework) {
if("1+1=?".equals(homework)) {
return "2";
return null;
程序执行作业本
作业:1+1=? 答案:2
怒,说好的回调方法呢~~因为到目前为止,不需要使用回调方法。技术总是伴随新的需求出现的。好,给你新的需求。注意重点来了我们回顾下这两行代码#室友写好作业
String theAnswer = roomMate.getAnswer(aHomeWork);
#该同学直接抄答案,完成作业
student.doHomeWork(aHomeWork, theAnswer);
该同学想了想,你给了答案有屁用,还得要我自己誊写到作业本上面去(执行自己的做作业方法)。你就不能直接调用我的做作业方法帮我把答案写好,把作业做完得了。让室友直接把作业写了经不住该同学的软磨硬泡,“中国好室友”答应了。怎么实现呢。再回顾下做作业的全过程#待解决的作业
String aHomeWork = "1+1=?";
#室友写出答案
String theAnswer = roomMate.getAnswer(aHomeWork);
#该同学调用,自己把答案写到作业本。(也即是这个步骤不给调用了)
student.doHomeWork(aHomeWork, theAnswer);
#做作业必须得调用这个方法,而根据需求这个方法必须由室友去调用。那很显然,该室友得保持一个该同学的引用,才能正常调用啊。
#室友说,那你在调用getAnswer方法的时候,除了传入作业,还需要把自己的引用放里面。这样我做完了,直接调用你的做作业方法就行了。
roomMate.getAnswer(aHomeWork,student);
完整代码public class Student {
public void doHomeWork(String homeWork, String answer) {
System.out.println("作业本");
if(answer != null) {
System.out.println("作业:"+homeWork+" 答案:"+ answer);
System.out.println("作业:"+homeWork+" 答案:"+ "(空白)");
public static void main(String[] args) {
Student student = new Student();
String aHomeWork = "1+1=?";
RoomMate roomMate = new RoomMate();
roomMate.getAnswer(aHomeWork,student);
public class RoomMate {
public void getAnswer(String homework, Student student) {
if("1+1=?".equals(homework)) {
student.doHomeWork(homework, "2");
student.doHomeWork(homework, "(空白)");
执行程序作业本
作业:1+1=? 答案:2
回调方法在上述“让室友直接把作业写了”的例子中,其实已经体现了回调的意思。场景的核心在于这位学生要把作业给做了。简单点描述:这位学生告诉室友要做什么作业,并把自己的引用也给了室友。该室友得到作业,做完后直接引用该学生并调用其做作业的方法,完成代写作业的任务。稍微复杂点描述:该学生做作业的方法有两个入参,一个是作业题目(已知),一个是作业答案(未知)。室友为了帮助他写作业提供了一个方法,该方法有两个入参,一个是作业题目,一个是该学生的引用(解出答案得知道往哪写)。程序执行时,该学生只要调用室友的代写作业方法就行了。一旦室友得到答案,因为有该学生的引用,所以直接找到对应方法,帮助其完成作业。再复杂点描述:学生调用室友的替写作业方法,注册了题目和自己的引用。室友的替写作业方法被调用,则会根据题目完成作业后,再回调该同学写作业方法,完成作业。再抽象点描述:类A调用类B的方法b(传入相关信息),类B的方法在执行完后,会将结果写到(再回调)类A的方法a,完成动作。(其实方法a就是传说中的回调方法啦)最抽象的描述:调用,回调。接口方式的回调方法常常使用回调方法的同学可能会说,我从来也没见过直接把对象的引用写到第一次调用方法里面的。嗯,是的,下面就来填上述例子留下的“天坑”(实际项目中常见到)。问题:在调用方法中直接传对象引用进去有什么不好?只说一点,只是让别人代写个方法,犯得着把自己全部暴露给别人吗。万一这个别人是竞争对手的接口咋办。这就是传说中的后面代码吗(/tx)。总之这样做是非常不安全的。因此,最常规的《调用,回调》实现,是(你已经猜到了)使用接口作为引用(说的不严谨)传入调用的方法里面。我承认,怎么将思路跳转到使用接口的花了我好长时间。我们再看RoomMate类的getAnswer方法。public class RoomMate {
public void getAnswer(String homework, Student student) {
if("1+1=?".equals(homework)) {
student.doHomeWork(homework, "2");
student.doHomeWork(homework, "(空白)");
关键在于,该方法的用途是来解决某学生提出的某个问题。答案是通过学生的doHomeWork方法回调传回去的。那假设有个工人也有问题,这位室友该怎么解决呢。再开个方法,专门接收工人的引用作为传参?当然不用,只要你这个引用包含了doHomeWork()方法,那么不论你是工人、警察还是环卫工人,直接调用getAnswer()方法就能解决你提的问题。至此我们的思路达到了:所有的对象要有同一个方法,所以自热而然就引出了接口概念。只要这些对象都实现了某个接口就行了,这个接口的作用,仅仅是用来规定那个做作业的方法长什么样。这样工人实现了该接口,那么就有了默认继承的做作业方法。工人再把自己的引用抛给该室友的时候,这个室友就不需要改动任何代码,直接接触答案,完成任务了。创建一个做作业的接口,专门规定,需要哪些东西(问题和答案)就能做作业.public interface DoHomeWork {
void doHomeWork(String question, String answer);
改动下中国好室友的解答方法。任意一个实现了DoHomeWork 接口的someone,都拥有doHomeWork(String question,String answer)的方法。这个方法就是上面已经提到的“回调方法”。someone先调用下好室友的getAnswer()方法,把问题和自己传进来(此为调用),好室友把问题解答出之后,调用默认提供的方法,写完作业。思考下,因为是以接口作为参数类型的约定,在普通对象upcast向上转型之后将只暴露接口描述的那个方法,别人获取到这个引用,也只能使用这个(回调)方法。至此,遗留的重大安全隐患重要解决。完整代码public class RoomMate {
public void getAnswer(String homework, DoHomeWork someone) {
if("1+1=?".equals(homework)) {
someone.doHomeWork(homework, "2");
someone.doHomeWork(homework, "(空白)");
package org.futeng.designpattern.callback.test1;
public class Worker implements DoHomeWork {
public void doHomeWork(String question, String answer) {
System.out.println("作业本");
if(answer != null) {
System.out.println("作业:"+question+" 答案:"+ answer);
System.out.println("作业:"+question+" 答案:"+ "(空白)");
public static void main(String[] args) {
Worker worker = new Worker();
String question = "1+1=?";
new RoomMate().getAnswer(question, worker);
执行程序作业本
作业:1+1=? 答案:2
至此,调用+回调的文章是不是写完了呢。咳咳,还木有。大家喝点茶再忍耐下。(我都写一天了 - -)常规使用之匿名内部类作为平凡的屁民,实用主义是我们坚持的生存法则。所以凡事用不到的技术都可以不学,凡事学了却不用的技术等于白学。我们之前已经定性,中国好室友RoomMate类拥有接受任何人任何问题挑战的潜质。自从好室友出名之后,有个不知道什么工作(类型)的人也来问问题。反正只要实现了回调接口,好室友都能调用你默认继承的回调方法,那就放马过来吧。package org.futeng.designpattern.callback.test1;
public class RoomMate {
public void getAnswer(String homework, DoHomeWork someone) {
if("1+1=?".equals(homework)) {
someone.doHomeWork(homework, "2");
someone.doHomeWork(homework, "(空白)");
public static void main(String[] args) {
RoomMate roomMate = new RoomMate();
roomMate.getAnswer("1+1=?", new DoHomeWork() {
public void doHomeWork(String question, String answer) {
System.out.println("问题:"+question+" 答案:"+answer);
看到稍显奇怪的roomMate.getAnswer("1+1=?", new DoHomeWork() {的哪一行,其实这里new的是DoHomeWork接口的一个匿名内部类。这里我想大家应该自己动脑想想,调用+反调,这个过程是怎么实现的了。至于是否使用匿名内部类是根据具体使用场景决定的。普通类不够直接,匿名内部类的语法似乎也不够友好。开源工具中对回调方法的使用上述匿名内部类的示例才是开源工具中常见到的使用方式。调用roomMate解答问题的方法(传进去自己的引用),roomMate解决问题,回调引用里面包含的回调方法,完成动作。roomMate就是个工具类,“调用”这个方法你传进去两个参数(更多也是一个道理),什么问题,问题解决了放哪,就行了。该“调用”方法根据问题找到答案,就将结果写到你指定放置的位置(作为回调方法的入参)。试想下,“中国好室友”接收的问题是SQL语句,接收的放置位置是我的引用。你解决问题(执行完SQL),将答案(SQL的反馈结果)直接写入我的回调方法里面。回调方法里面可能包括一个个的字段赋值。但是在调用层面隐藏了很多细节的处理。这是回调方法的一个小小的优势。再换句话说,不需要拿到执行完SQL之后的返回结果一个个来赋值。SQL的例子 public static List&Person& queryPerson() {
QueryRunner queryRunner = new QueryRunner(DataSourceSupport.getDataSource());
return queryRunner.query(" select t.name, t.age from person t ", new ResultSetHandler&List&Person&&(){
List list = new ArrayList&Person&();
public List&Person& handle(ResultSet rs) throws SQLException {
while(rs.next()) {
Person person = new Person();
person.setName(rs.getString(0));
person.setAge(rs.getInt(1));
list.add(person);
return list;
回调方法的优势回调方法最大的优势在于,异步回调,这样是其最被广为使用的原因。下面将沿用“中国好室友” 来对回调方法做异步实现。回调接口不用变public interface DoHomeWork {
void doHomeWork(String question, String answer);
为了体现异步的意思,我们给好室友设置了个较难的问题,希望好室友能多好点时间思考。Student student = new Student();
String homework = "当x趋向于0,sin(x)/x =?";
#给学生新建个ask方法,该方法中另开一个线程,来等待回调方法的结果反馈。
student.ask(homework, new RoomMate());
#ask方法如下
public void ask(final String homework, final RoomMate roomMate) {
new Thread(new Runnable() {
public void run() {
roomMate.getAnswer(homework, Student.this);
}).start();
#新开的线程纯粹用来等待好室友来写完作用。由于在好室友类中设置了3秒的等待时间,所以可以看到goHome方法将先执行。
#意味着该学生在告知好室友做作用后,就可以做自己的事情去了,不需要同步阻塞去等待结果。
#一旦好室友完成作用,写入作业本,该现场也就结束运行了。
完整代码public class Student implements DoHomeWork{
public void doHomeWork(String question, String answer) {
System.out.println("作业本");
if(answer != null) {
System.out.println("作业:"+question+" 答案:"+ answer);
System.out.println("作业:"+question+" 答案:"+ "(空白)");
public void ask(final String homework, final RoomMate roomMate) {
new Thread(new Runnable() {
public void run() {
roomMate.getAnswer(homework, Student.this);
}).start();
public void goHome(){
System.out.println("我回家了……好室友,帮我写下作业。");
public static void main(String[] args) {
Student student = new Student();
String homework = "当x趋向于0,sin(x)/x =?";
student.ask(homework, new RoomMate());
public class RoomMate {
public void getAnswer(String homework, DoHomeWork someone) {
if ("1+1=?".equals(homework)) {
someone.doHomeWork(homework, "2");
} else if("当x趋向于0,sin(x)/x =?".equals(homework)) {
System.out.print("思考:");
for(int i=1; i&=3; i++) {
System.out.print(i+"秒 ");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println();
someone.doHomeWork(homework, "1");
someone.doHomeWork(homework, "(空白)");
完结至此回调方法的介绍告一段落。趁着是高考日,表示要跟考生感同身受一下。特意花了整整一个白天的时间写就这篇文章。全文一蹴而就并没有更多时间来修改,可能隐藏着诸多错误,行文也可能不甚通顺,还请各位指正和海涵。 19:24
回调函数:函数可以理解为一个功能体,执行它可以完成一个任务。回调函数本质上就是一个函数,只是执行时间和执行主体与普通的函数稍有区别。-----举个例子的分割线------------你去食堂打饭,你喜欢吃小炒热饭菜,所以你去了一个小炒窗口。你跟老板说了要×××盖饭,老板说:你是100号,喊到你的号你就来拿菜。然后你在旁边跟同学吹牛、或者看手机、或者干点你想干的任何事情。。。然后你听到老板喊100号并且把菜放到窗口,你走到窗口,拿到你的菜。这里面就有典型的异步操作、回调函数的概念。-----下面很烦的分割线------------好吧,先搞清楚一些问题再回头分析回调函数在什么场景有用?我要在特定时候执行一个任务,至于是什么时候我自己都不知道。比如某一时间到了或者某一事件发生或者某一中断触发。回调函数怎么起作用?把我要执行的这个任务写成一个函数,将这个函数和某一时间或者事件或者中断建立关联。当这个关联完成的时候,这个函数华丽的从普通函数变身成为回调函数。回调函数什么时候执行?当该回调函数关心的那个时间或者事件或者中断触发的时候,回调函数将被执行。一般是触发这个时间、事件或中断的程序主体(通常是个函数或者对象)观察到有一个关注这个东东的回调函数的时候,这个主体负责调用这个回调函数。回调函数有什么好处?最大的好处是你的程序变成异步了。也就是你不必再调用这个函数的时候一直等待这个时间的到达、事件的发生或中断的发生(万一一直不发生,你的程序会怎么样?)。再此期间你可以做做别的事情,或者四处逛逛。当回调函数被执行时,你的程序重新得到执行的机会,此时你可以继续做必要的事情了。回调函数有什么问题吗?既然有人问,当然就会有点问题。一个是学习成本会比普通函数高,需要有一定的抽象思维能力,需要对应用场景的理解。另一个是回调函数很多情况下会附带有跨线程操作甚至于跨进程的操作,这些都是异步带来的成本。-----回调分析的分割线------------你去食堂打饭,你喜欢吃小炒热饭菜,所以你去了一个小炒窗口。你跟老板说了要×××盖饭,老板说:你是100号,喊到你的号你就来拿菜。然后你在旁边跟同学吹牛、或者看手机、或者干点你想干的任何事情。。。然后你听到老板喊100号并且把菜放到窗口,你走到窗口,拿到你的菜。这里面有几个函数:老板的部分:1、老板提供一个点餐的函数 boss.Order(string 菜名,double 钱)2、老板有个做饭的函数,此函数耗时较长boss.Cook()3、老板提供一个事件,当boss.cook()执行完时,该事件被触发,boss.OnCookF你的部分:1、你需要有一个函数去订餐,也就是你的函数中需要执行类似于boss.Order("红烧肉盖浇饭",20),比如是me.Hungry()2、你需要有一个函数作为回调函数去关注boss.OnCookFinish事件,这样当老板做好饭,你就可以知道是不是你的好了。由于老板的事件发生的时候中会喊编号并且吧菜放到窗口,所以你的回调函数需要能够接受1个编号和1个菜作为参数。比如me.AcceptFood(int currNumber,object food)所以整个程序的流程其实是这样的。me.Hungry(){ boss.Order("红烧肉盖浇饭",20); boss.OnCookFinish+=me.AcceptF//此处表面,AcceptFood这个回调函数关心OnCookFinish事件,并且变成这个事件的回调函数 //此时这个函数执行完,不再等待}boss.Order("红烧肉盖浇饭",20){ //收钱 //配菜
前2个耗时较短 boss.Cook();//此处一般会开新线程执行cook动作}boss.Cook(){ //cooking~~~~~~~~~~ //完成了,下面将要触发事件,系统将检查这个事件是否有回调函数关心,有的话逐个回调。 OnCookFinish(100号,红烧肉盖浇饭);}至此案例基本完成了一个完整的任务流程。======最终总结的分割线==========回调函数在异步处理过程中的一个必要手段。目的是让me不需要等boss的长时间操作,可以在这段时间做点别的事情。------关于硬件中断------------硬件中断也会有对应的函数做处理,所以这个函数从概念上来说也就是个回调函数。无非前文讨论的是软件层面的,硬件中断对应的函数是OS层面甚至于硬件层面的。没什么本质的区别。
好莱坞准则:Don't call me; I'll call you!
这里讲的是异步回调(谢谢评论中 @羊羊羊 提醒),或者是远程过程调用(RPC)语义上的回调。小明有一道数学题不懂,打电话给同学小军,小军听完题后感觉不能马上回答,解题得花些时间。这时候他们有几种选择:选择1,电话不挂(也可以挂了),小军解题,每过一会儿,小明就(打电话)问,怎么样了?问了十几分钟,小军才解答出来,告诉小明。选择2,电话挂了,小明把自己的电话告诉小军,小军做完后给小明打电话告诉小明答案。选择3,电话挂了,但小军不愿意花电话费给小明讲题,于是约定响铃三声,小明别接,然后再打回来。选择4,电话挂了,小军告诉小明自己的博客地址,解完了会写在博客上,小明做完其他作业后再去看。第一种方法,小明在小军解题期间不能放下电话,只能不断询问。第二种方法,小明在小军解题期间可以做其他作业,当小军打电话来时,又得放下其他作业。小明给小军留下电话号码,让他拨回来,就被称作回调(call back)第三种方法,小明可以做其他作业,当小军打电话来响铃时,就知道小军已经解完题了,可以马上,或在合适的时间去打电话给小军。这种就不叫回调了。第四种方法,小明可以先做完其他作业后去小明的博客看就行了。
讲起来确实乱,比较准确的描述是CPS的一种使用。即把一个continuation(回调函数/callback)传递(pass)给处理过程。这里回调函数是一段具象化(Reification)后的代码。上面这点看不懂不影响你参照别的答案里比较具体的例子来使用回调函数,不过我还是希望能比较准确的描述一下什么是callback。
回调函数的意思是“把函数A作为参数传递给另一个函数B”。维基百科的解释:a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at some convenient time.为什么要这样做呢?为什么不把定义好的函数放在头文件中呢?因为可以作为参数的函数A的名称和实现可以被抽象出来,也就是在写函数B的时候,不需要管函数A叫什么名字,怎么实现,只要函数A的返回类型是一定的就可以了(泛型的话连这个也不需要)。比如某人写了一个画图的库,把具体怎么画留给使用库的人定义。使用库的人就可以自定义画图函数的名称和实现方式。例子(C++):非泛型定义库(library.h)#include &cstdio&
void fill_screen(int (*some_func)(void)) {printf("%d\n",some_func());}
使用库的时候:#include "library.h"
int draw_this_way() {return 0;}
int draw_that_way() {return 1;}
int draw_yet_another_way() {return 2;}
int main() {
fill_screen(&draw_this_way);
fill_screen(&draw_that_way);
fill_screen(&draw_yet_another_way);
编译:g++ callback.cpp -o callback
结果:$./callback
泛型定义库(library.h):# include &iostream&
template&typename ANY_TYPE&
void fill_screen(ANY_TYPE (*some_func)(void)) { std::cout && some_func() && std::endl;}
使用库的时候:#include "library.h"
int draw_this_way() {return 0;}
char draw_that_way() {return 'a';}
bool draw_yet_another_way() {return true;}
int main() {
fill_screen(&draw_this_way);
fill_screen(&draw_that_way);
fill_screen(&draw_yet_another_way);
编译:g++ callback.cpp -o callback
结果:$./callback
补充:委托和回调委托是一种设计模式。意思是A类把自己的工作交给B类完成。简单的委托相当与在A的方法中调用了B的方法。使用委托可以把库的定义和具体使用分开。比如数据库的基本操作是CRUD,但是每个不同数据库具体api不同,对于使用者而言不可能逐个掌握所有数据库的api,所以出现了api的wrapper,用户只用掌握一套CRUD的api就可以使用所有数据库了。在某些不能直接将函数作为参数传递的语言(比如java)中,委托是实现回调功能的一种方式。具体而言,库的作者定义一个接口,在他的方法中调用这个接口,但是把接口的实现留给具体使用的人。在使用的时候,使用者新建一个匿名接口,并且重载要调用的接口方法。 这个过程相当与库的作者将工作通过接口“委托”给了使用者。这个委托的过程相对比较复杂,打个生活中的比方:“领导不但把工作推脱给下属,还要对他们具体怎么干指手画脚。”这里的领导是库的使用者,下属是库的定义,推脱是指调用下属的工作函数,指手画脚是指对工作函数调用的接口给予具体定义。以下是java的例子。定义库 (DrawLibrary.java)public class DrawLibrary {
public interface DrawInterface
int draw();
public static void fillScreen(DrawInterface someObj) {
System.out.println(someObj.draw());
使用库(Graphics.java)public class Graphics {
public static void drawThisWay() {
DrawLibrary.fillScreen( new DrawLibrary.DrawInterface() {
public int draw() {
public static void drawThatWay() {
DrawLibrary.fillScreen( new DrawLibrary.DrawInterface() {
public int draw() {
public static void drawYetAnotherWay() {
DrawLibrary.fillScreen( new DrawLibrary.DrawInterface() {
public int draw() {
public static void main(String[] args) {
drawThisWay();
drawThatWay();
drawYetAnotherWay();
编译:$javac Graphics.java
$java Graphics
这种设计模式经常在事件驱动的设计中看到,比如监听按钮是否被按下等等。
已有帐号?
无法登录?
社交帐号登录

我要回帖

更多关于 matlab 求传递函数 的文章

 

随机推荐