什么是回调函数?

什么是回调函数?

442845 次浏览

维基百科上的回调页很好地解释了这一点:

在计算机编程中,回调是对执行代码的引用,或作为参数传递给其他代码的一段执行代码。这允许较低级别的软件层调用在较高级别定义的子程序(或函数)。

回调函数是您为现有函数/方法指定的函数,在操作完成、需要额外处理等时调用。

例如,在Javascript中,或者更具体地说,在jQuery中,您可以指定一个回调参数,以便在动画完成时调用。

在PHP中,preg_replace_callback()函数允许您提供一个将在匹配正则表达式时调用的函数,将匹配的字符串作为参数传递。

一个重要的使用区域是,您将一个函数注册为句柄(即回调),然后发送消息/调用某个函数来做一些工作或处理。现在处理完成后,被调用的函数将调用我们注册的函数(即现在回调完成),从而表明我们的处理完成了。
这个 wikipedia链接用图形很好地解释了。

回调函数是在满足特定条件时应该调用的函数。回调函数不是立即调用,而是在未来的某个时间点调用。

通常,它在启动将异步完成的任务时使用(即在调用函数返回后的一段时间内完成)。

例如,请求网页的函数可能要求其调用者提供一个回调函数,该函数将在网页完成下载时调用。

这使得回调听起来像方法末尾的返回语句。

我不确定他们是什么。

我认为回调实际上是对一个函数的调用,这是另一个函数被调用和完成的结果。

我也认为回调是为了解决原始调用,在一种“嘿!你要求的那件事?我已经做了-只是想让你知道-回到你身边”。

这个问题的简单答案是回调函数是通过函数指针调用的函数。如果您将函数的指针(地址)作为参数传递给另一个函数,当该指针用于调用它指向的函数时,就会说进行了回调

外行人的回答是,它是一个不是由您调用的函数,而是由用户或浏览器在特定事件发生或处理某些代码后调用的函数。

开发人员经常对回调是什么感到困惑,因为该死的东西的名称。

回调函数是这样一个函数:

  • 由另一个函数访问,并且
  • 如果第一个函数完成,则在第一个函数之后调用

想象回调函数如何工作的一个好方法是,它是一个函数,它被传递到的函数的“在后面叫”。

也许更好的名称是“之后调用”函数。

此构造对于异步行为非常有用,我们希望在上一个事件完成时发生活动。

伪代码:

// A function which accepts another function as an argument// (and will automatically invoke that function when it completes - note that there is no explicit call to callbackFunction)funct printANumber(int number, funct callbackFunction) {printout("The number you provided is: " + number);}
// a function which we will use in a driver function as a callback functionfunct printFinishMessage() {printout("I have finished printing numbers.");}
// Driver methodfunct event() {printANumber(6, printFinishMessage);}

如果您调用事件(),则结果:

The number you provided is: 6I have finished printing numbers.

这里输出的顺序很重要。由于回调函数是在之后调用的,因此“我已经完成打印数字”是最后打印的,而不是第一个。

回调之所以被称为回调,是因为它们与指针语言一起使用。如果你不使用其中之一,不要为“回调”这个名字而苦恼。只要理解它只是一个描述作为另一个方法参数提供的方法的名称,这样当父方法被调用时(无论什么条件,如按钮单击、计时器滴答声等)及其方法主体完成,回调函数就会被调用。

有些语言支持支持多个回调函数参数的构造,并根据父函数的完成方式调用(即,在父函数成功完成的事件中调用一个回调,在父函数抛出特定错误的事件中调用另一个回调,等等)。

看看图片:)这就是它的工作原理

主程序调用具有回调函数名称的库函数(也可能是系统级函数)。此回调函数可以多种方式实现。主程序根据要求选择一个回调。

最后,库函数在执行过程中调用回调函数。

我相信这个“回调”术语在很多地方都被错误地使用了。我的定义是这样的:

回调函数是您传递给某人并让其#36825;在某个时间点进行?

我想人们只是阅读了wiki定义的第一句话:

回调是对执行代码的引用,或者是执行代码,作为参数传递给其他代码。

我一直在使用很多API,看到各种不好的例子。许多人倾向于将函数指针(对执行代码的引用)或匿名函数(一段执行代码)命名为“回调”,如果它们只是函数,为什么需要另一个名称?

实际上只有wiki定义中的第二句话揭示了回调函数和普通函数之间的区别:

这允许较低级别的软件层调用子例程(或函数)在更高级别的层中定义。

所以区别在于你将传递给谁函数以及你传入的函数将如何被调用。如果你只是定义一个函数并将其传递给另一个函数并直接在该函数体中调用它,不要称其为回调。定义说你传入的函数将被“低级”函数调用。

我希望人们能停止在模棱两可的语境中使用这个词,它不能帮助人们更好地理解,只会更糟。

假设我们有一个函数sort(int *arraytobesorted,void (*algorithmchosen)(void)),它可以接受一个函数指针作为它的参数,这个参数可以在sort()的实现中的某个时候使用。然后,这里由函数指针algorithmchosen寻址的代码被称为回调函数

看到的优点是,我们可以选择任何算法,如:

  1.    algorithmchosen = bubblesort2.    algorithmchosen = heapsort3.    algorithmchosen = mergesort   ...

比如说,已经用原型实现了:

  1.   `void bubblesort(void)`2.   `void heapsort(void)`3.   `void mergesort(void)`   ...

这是一个用于实现面向对象编程中的多态性的概念

后打电话比愚蠢的名字回调更好。当或如果在函数中满足条件时,调用另一个函数,后打电话函数,作为参数接收的函数。

不是在函数中硬编码内部函数,而是编写一个函数来接受已经编写的后打电话函数作为参数。后打电话可能会根据接收参数的函数中的代码检测到的状态更改来调用。

回调最容易用电话系统来描述。函数呼叫类似于打电话给某人,问她一个问题,得到答案,然后挂断;添加回调改变了类比,这样在问她一个问题后,你也给她你的名字和电话号码,这样她就可以给你回电话。

——Paul Jakubik,“C++中的回调实现”

不透明定义

回调函数是您提供给另一段代码的函数,允许该代码调用它。

做作的例子

你为什么要这样做?假设你需要调用一个服务。如果服务立即返回,你只需:

  1. 叫它
  2. 等待结果
  3. 结果出来后继续

例如,假设服务是factorial函数。当你想要5!的值时,你会调用factorial(5),然后会发生以下步骤:

  1. 您当前的执行位置被保存(在堆栈上,但这并不重要)

  2. 行刑移交给factorial

  3. factorial完成时,它会将结果放在您可以到达的地方

  4. 死刑又回到了[1]

现在假设factorial花了很长时间,因为你给了它巨大的数字,它需要在某个超级计算集群上运行。假设你预计需要5分钟才能返回你的结果。你可以:

  1. 保持你的设计,在晚上睡觉的时候运行你的程序,这样你就不会有一半的时间盯着屏幕

  2. factorial在做它的事情时,设计你的程序来做其他事情

如果您选择第二个选项,那么回调可能适合您。

端到端设计

为了利用回调模式,您希望能够以以下方式调用factorial

factorial(really_big_number, what_to_do_with_the_result)

第二个参数what_to_do_with_the_result是您发送给factorial的函数,希望factorial在返回之前会根据其结果调用它。

是的,这意味着需要编写factorial来支持回调。

现在假设您希望能够将参数传递给您的回调。现在你不能,因为你不会调用它,factorial是。所以需要编写factorial来允许您传递参数,当它调用它时,它会将它们传递给您的回调。它可能看起来像这样:

factorial (number, callback, params){result = number!   // i can make up operators in my pseudocodecallback (result, params)}

现在factorial允许这种模式,你的回调可能如下所示:

logIt (number, logger){logger.log(number)}

你对factorial的调用将是

factorial(42, logIt, logger)

如果你想从logIt返回一些东西怎么办?嗯,你不能,因为factorial没有注意它。

那么,为什么factorial不能只返回您的回调返回的内容呢?

使其不阻塞

由于执行应该在factorial完成时移交给回调,所以它真的不应该向调用者返回任何东西。理想情况下,它会以某种方式在另一个线程/进程/机器中启动它的工作并立即返回,以便你可以继续,也许是这样的:

factorial(param_1, param_2, ...){new factorial_worker_task(param_1, param_2, ...);return;}

这现在是一个“异步调用”,这意味着当你调用它时,它会立即返回,但还没有真正完成它的工作。所以你确实需要机制来检查它,并在完成时获得它的结果,而你的程序在这个过程中变得更加复杂。

顺便说一句,使用这种模式,factorial_worker_task可以异步启动回调并立即返回。

那你是做什么的?

答案是保持在回调模式内。无论何时你想写

a = f()g(a)

并且f是异步调用的,您将改为编写

f(g)

其中g作为回调传递。

这从根本上改变了程序的流程拓扑,需要一些习惯。

你的编程语言可以为你提供一种动态创建函数的方法,从而为你提供很多帮助。在上面的代码中,函数g可能和print (2*a+1)一样小。如果你的语言要求你将其定义为一个单独的函数,使用完全不必要的名称和签名,那么如果你经常使用这种模式,你的生活将会变得不愉快。

另一方面,如果你的语言允许你创建lambdas,那么你的状态就会好得多。然后你最终会写出这样的东西

f( func(a) { print(2*a+1); })

这样就好多了。

回调如何通过

你如何将回调函数传递给factorial?嗯,你可以通过多种方式做到这一点。

  1. 如果被调用的函数在同一个进程中运行,您可以传递一个函数指针

  2. 或者你想在你的程序中维护一个fn name --> fn ptr的字典,在这种情况下,你可以传递这个名字

  3. 也许您的语言允许您就地定义函数,可能是lambda!在内部它正在创建某种对象并传递指针,但您不必担心。

  4. 也许您正在调用的函数正在完全独立的机器上运行,并且您正在使用HTTP等网络协议调用它。您可以将回调公开为HTTP可调用函数,并传递其URL。

你懂的。

最近回调的上升

在我们进入的这个网络时代,我们调用的服务通常是通过网络。我们经常无法控制这些服务,即我们没有编写它们,我们没有维护它们,我们无法确保它们正常运行或它们的性能如何。

但是我们不能指望我们的程序在等待这些服务响应时阻塞。意识到这一点,服务提供者经常使用回调模式设计API。

JavaScript非常好地支持回调,例如lambdas和闭包。JavaScript世界中有很多活动,无论是在浏览器上还是在服务器上。甚至有JavaScript平台正在为移动设备开发。

随着我们的前进,越来越多的人将编写异步代码,对此的理解将是必不可少的。

回调函数,也称为高阶函数,是作为参数传递给另一个函数的函数,回调函数在父函数内部被调用(或执行)。

$("#button_1").click(function() {alert("button 1 Clicked");});

这里我们将一个函数作为参数传递给Click方法。Click方法将调用(或执行)我们传递给它的回调函数。

回调与回调函数

回调是一个在另一个函数完成执行后要执行的函数——因此得名“回电”

什么是回调函数

  • 将Funs(即函数对象)作为参数或返回Funs的函数称为高阶函数。
  • 任何作为参数传递的函数都称为回调函数。
  • 回调函数是作为参数传递给另一个函数(让我们调用这个另一个函数otherFunction)的函数,回调函数在otherFunction内部调用(或执行)。
    function action(x, y, callback) {return callback(x, y);}    
function multiplication(x, y) {return x * y;}    
function addition(x, y) {return x + y;}
alert(action(10, 10, multiplication)); // output: 100
alert(action(10, 10, addition)); // output: 20

在SOA中,回调允许插件模块从容器/环境访问服务。

来源

让我们保持简单。什么是回调函数?

用比喻和类比举例

我有一个秘书,每天我都让她:(i)把公司寄出去的邮件送到邮局,之后她已经这样做了,去做:(ii)我为她写的任何任务。

现在,便签上的任务是什么?任务每天都在变化。

假设在这个特殊的日子,我要求她打印一些文件。所以我把它写在便签上,然后把它和她需要寄出的邮件一起钉在她的桌子上。

总结:

  1. 首先,她需要把邮件和
  2. 后立即完成后,她需要打印一些文档。

回调函数是第二个任务:打印这些文档。因为它是在邮件被丢弃后完成的,也因为告诉她打印文档的便签和她需要邮寄的邮件一起给她。

现在让我们把它和编程词汇联系起来

  • 本例中的方法名称为:DropOffMail。
  • 回调函数是:PrintOffDocuments。PrintOffDocuments是回调函数,因为我们希望秘书这样做,只有在DropOffMail运行后。
  • 所以我会将:PrintOffDocuments作为“参数”传递给DropOffMail方法。这是一个重要的点。

仅此而已。仅此而已。我希望这能为您澄清-如果没有,请发表评论,我会尽力澄清。

在计算机编程中,回调是对执行代码的引用,或者是作为参数传递给其他代码的一段执行代码。这允许较低级别的软件层调用在较高级别中定义的子程序(或函数)

使用函数指针在C中回调

在C中,回调是使用函数指针实现的。函数指针-顾名思义,是指向函数的指针。

例如,int(*ptrFunc)();

在这里,ptrFunc是一个指向不接受参数并返回整数的函数的指针。不要忘记放在括号中,否则编译器将假设ptrFunc是一个普通函数名称,它不接受任何参数并返回一个指向整数的指针。

下面是一些代码来演示函数指针。

#include<stdio.h>int func(int, int);int main(void){int result1,result2;/* declaring a pointer to a function which takestwo int arguments and returns an integer as result */int (*ptrFunc)(int,int);
/* assigning ptrFunc to func's address */ptrFunc=func;
/* calling func() through explicit dereference */result1 = (*ptrFunc)(10,20);
/* calling func() through implicit dereference */result2 = ptrFunc(10,20);printf("result1 = %d result2 = %d\n",result1,result2);return 0;}
int func(int x, int y){return x+y;}

现在让我们尝试使用函数指针理解C中回调的概念。

完整的程序有三个文件:call back. c,reg_callback. h和reg_callback. c。

/* callback.c */#include<stdio.h>#include"reg_callback.h"
/* callback function definition goes here */void my_callback(void){printf("inside my_callback\n");}
int main(void){/* initialize function pointer tomy_callback */callback ptr_my_callback=my_callback;printf("This is a program demonstrating function callback\n");/* register our callback function */register_callback(ptr_my_callback);printf("back inside main program\n");return 0;}
/* reg_callback.h */typedef void (*callback)(void);void register_callback(callback ptr_reg_callback);

/* reg_callback.c */#include<stdio.h>#include"reg_callback.h"
/* registration goes here */void register_callback(callback ptr_reg_callback){printf("inside register_callback\n");/* calling our callback function my_callback */(*ptr_reg_callback)();}

如果我们运行这个程序,输出将是

这是一个演示函数回调的程序在register_callback在my_callback返回主程序

高层函数调用低层函数作为正常调用,回调机制允许低层函数通过指向回调函数的指针调用高层函数。

使用接口Java回调

Java没有函数指针的概念它通过接口机制实现回调机制这里不是函数指针,我们声明了一个接口,它有一个方法,当被调用者完成任务时将调用该方法

让我通过一个例子来说明:

回调接口

public interface Callback{public void notify(Result result);}

呼叫者或更高级别的类

public Class Caller implements Callback{Callee ce = new Callee(this); //pass self to the callee
//Other functionality//Call the Asynctaskce.doAsynctask();
public void notify(Result result){//Got the result after the callee has finished the task//Can do whatever i want with the result}}

Callee或较低层函数

public Class Callee {Callback cb;Callee(Callback cb){this.cb = cb;}
doAsynctask(){//do the long running task//get the resultcb.notify(result);//after the task is completed, notify the caller}}

使用EventListener模式的回调

  • 列表项

此模式用于通知0到n个观察者/侦听器,特定任务已完成

  • 列表项

Callback机制和EventListener/观察器机制的区别在于,在回调中,被调用者通知单个调用者,而在Eventlisener/观察器中,被调用者可以通知任何对该事件感兴趣的人(通知可能会转到应用程序中尚未触发任务的其他部分)

让我通过一个例子来解释。

事件界面

public interface Events {
public void clickEvent();public void longClickEvent();}

类小部件

package com.som_itsolutions.training.java.exampleeventlistener;
import java.util.ArrayList;import java.util.Iterator;
public class Widget implements Events{
ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>();ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();
@Overridepublic void clickEvent() {// TODO Auto-generated method stubIterator<OnClickEventListener> it = mClickEventListener.iterator();while(it.hasNext()){OnClickEventListener li = it.next();li.onClick(this);}}@Overridepublic void longClickEvent() {// TODO Auto-generated method stubIterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();while(it.hasNext()){OnLongClickEventListener li = it.next();li.onLongClick(this);}
}
public interface OnClickEventListener{public void onClick (Widget source);}
public interface OnLongClickEventListener{public void onLongClick (Widget source);}
public void setOnClickEventListner(OnClickEventListener li){mClickEventListener.add(li);}public void setOnLongClickEventListner(OnLongClickEventListener li){mLongClickEventListener.add(li);}}

类按钮

public class Button extends Widget{private String mButtonText;public Button (){}public String getButtonText() {return mButtonText;}public void setButtonText(String buttonText) {this.mButtonText = buttonText;}}

类复选框

public class CheckBox extends Widget{private boolean checked;public CheckBox() {checked = false;}public boolean isChecked(){return (checked == true);}public void setCheck(boolean checked){this.checked = checked;}}

活动类

包com.som_itsolutions.training.java.exampleeventlistener;

public class Activity implements Widget.OnClickEventListener{public Button mButton;public CheckBox mCheckBox;private static Activity mActivityHandler;public static Activity getActivityHandle(){return mActivityHandler;}public Activity (){mActivityHandler = this;mButton = new Button();mButton.setOnClickEventListner(this);mCheckBox = new CheckBox();mCheckBox.setOnClickEventListner(this);}public void onClick (Widget source){if(source == mButton){mButton.setButtonText("Thank you for clicking me...");System.out.println(((Button) mButton).getButtonText());}if(source == mCheckBox){if(mCheckBox.isChecked()==false){mCheckBox.setCheck(true);System.out.println("The checkbox is checked...");}else{mCheckBox.setCheck(false);System.out.println("The checkbox is not checked...");}}}public void doSomeWork(Widget source){source.clickEvent();}}

其他类

public class OtherClass implements Widget.OnClickEventListener{Button mButton;public OtherClass(){mButton = Activity.getActivityHandle().mButton;mButton.setOnClickEventListner(this);//interested in the click event                        //of the button}@Overridepublic void onClick(Widget source) {if(source == mButton){System.out.println("Other Class has also received the event notification...");}}

主类

public class Main {public static void main(String[] args) {// TODO Auto-generated method stubActivity a = new Activity();OtherClass o = new OtherClass();a.doSomeWork(a.mButton);a.doSomeWork(a.mCheckBox);}}

从上面的代码中可以看到,我们有一个名为Event的接口,它基本上列出了我们的应用程序可能发生的所有事件。Widget类是所有UI组件的基类,如Button、Checkbox。这些UI组件是实际从框架代码接收事件的对象。Widget类实现了Events接口,并且它有两个嵌套接口,即OnClickEventListener和OnLongClickEventListener

这两个接口负责监听可能在Widget派生的UI组件(如Button或Checkbox)上发生的事件。因此,如果我们将此示例与之前使用JavaInterface的Callback示例进行比较,这两个接口用作Callback接口。因此,更高级别的代码(此处为活动)实现了这两个接口。每当小部件发生事件时,都会调用更高级别的代码(或在更高级别代码中实现的这些接口的方法,此处为活动)。

现在让我讨论一下Callback和Eventlistener模式的基本区别。正如我们提到的,使用Callback,被调用者只能通知一个调用者。但是在EventListener模式的情况下,应用程序的任何其他部分或类都可以注册可能在按钮或复选框上发生的事件。这种类的例子是其他类。如果你看到其他类的代码,你会发现它已经注册为可能发生在活动中定义的按钮中的ClickEvent的侦听器。有趣的是,除了活动(调用者),每当按钮上发生单击事件时,这个其他类也会得到通知。

回调函数作为参数传递给另一个函数的函数。

function test_function(){alert("Hello world");}
setTimeout(test_function, 2000);

备注:在上面的示例中test_function用作setTimeout函数的参数。

回调函数是您传递(作为引用或指针)给某个函数或对象的函数。此函数或对象将在以后的任何时间调用此函数,可能多次,出于任何目的:

  • 通知任务结束
  • 请求两个项目之间的比较(如c qsort())
  • 报告进程的进展
  • 通知事件
  • 委托对象的实例化
  • 委托绘画一个区域

因此,将回调描述为在另一个函数或任务结束时调用的函数过于简化(即使它是一个常见的用例)。

回调是将一个函数作为参数传递给另一个函数的想法,并在过程完成后调用这个函数。

如果你通过上面的精彩答案了解了回调的概念,我建议你应该了解它的背景。

“是什么让他们(计算机科学家)开发回调?”你可能会遇到一个问题,那就是阻塞。(尤其是阻塞UI)回调并不是唯一的解决方案。还有很多其他解决方案(例如:线程,期货,承诺……)。

我在这个答案上迟到了13年,但在自己学习之后,我想我会在这里留下另一个答案,以防有人像我一样困惑。

其他答案总结了“什么是回调”这个问题的症结

它只是一个函数,当某些事情完成时调用另一个函数。

得到我的是例子,“你做了这个,现在做那个。”就像为什么一样,当我可以自己调用一个方法或函数时,我会这样使用它吗?

所以这里有一个快速的,真实世界的例子,希望能让它“点击”某人。

超伪代码

首先,您将遇到的核心问题……

Multithreaded Method(Some arguments){Do fancy multithreaded stuff....}
Main(){Some stuff I wanna do = some tasksMulthreaded Method(Some stuff I wanna do)}

如果您在没有任何回调的情况下运行它,您的程序看起来就像它刚刚退出一样。因为“花哨的多线程东西”正在另一个进程上运行。

所以你挠挠头,想:“好吧,见鬼,我怎么知道什么时候完成?”

BOOM…回电

IsItDone = false
Callback(){print("Hey, I'm done")IsItDone = true}  
Multithreaded Method(Some arguments, Function callback){Do fancy multithreaded stuff....}
Main(){Some stuff I wanna do = some tasksMulthreaded Method(Some stuff I wanna do,Callback)
while(!IsItDone)Wait a bit}

这不是100%实现它的最佳方式,我只是想举一个明确的例子。

所以这不是赤裸裸的“什么是回调?”它是“什么是回调,它对我有什么好处???”