什么是回调函数?
当前回答
“在计算机编程中,回调是对可执行代码或一段可执行代码的引用,作为参数传递给其他代码。这允许较低级别的软件层调用在较高级别中定义的子例程(或函数)。”-维基百科
使用函数指针在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 takes
two 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中回调的概念。
整个程序有三个文件:callback.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 to
my_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)();
}
如果我们运行这个程序,输出将是
这是一个演示函数回调的程序内部寄存器回调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 Asynctask
ce.doAsynctask();
public void notify(Result result){
//Got the result after the callee has finished the task
//Can do whatever i want with the result
}
}
被调用者或下层函数
public Class Callee {
Callback cb;
Callee(Callback cb){
this.cb = cb;
}
doAsynctask(){
//do the long running task
//get the result
cb.notify(result);//after the task is completed, notify the caller
}
}
使用EventListener模式的回调
列表项目
此模式用于通知0到n个观察者/收听者特定任务已完成
列表项目
Callback机制和EventListener/Observer机制的区别在于,在回调中,被调用者通知单个调用者,而在Eventlisener/Oobserver中,被调用者可以通知对该事件感兴趣的任何人(通知可能会发送到应用程序中未触发任务的其他部分)
让我通过一个例子来解释一下。
事件界面
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>();
@Override
public void clickEvent() {
// TODO Auto-generated method stub
Iterator<OnClickEventListener> it = mClickEventListener.iterator();
while(it.hasNext()){
OnClickEventListener li = it.next();
li.onClick(this);
}
}
@Override
public void longClickEvent() {
// TODO Auto-generated method stub
Iterator<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.exampleventlistener;
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
}
@Override
public 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 stub
Activity a = new Activity();
OtherClass o = new OtherClass();
a.doSomeWork(a.mButton);
a.doSomeWork(a.mCheckBox);
}
}
从上面的代码中可以看到,我们有一个名为events的接口,它基本上列出了应用程序可能发生的所有事件。Widget类是所有UI组件(如Button、Checkbox)的基类。这些UI组件是实际从框架代码接收事件的对象。Widget类实现Events接口,并且它有两个嵌套接口,即OnClickEventListener和OnLongClickEventListen
这两个接口负责监听Widget派生的UI组件(如Button或Checkbox)上可能发生的事件。因此,如果我们将此示例与前面使用Java接口的回调示例进行比较,则这两个接口用作回调接口。因此,更高级别的代码(此处活动)实现了这两个接口。每当小部件发生事件时,将调用更高级别的代码(或在更高级别代码中实现的这些接口的方法,这里是Activity)。
现在让我讨论回调和Eventlistener模式之间的基本区别。正如我们提到的,使用Callback,被调用者只能通知单个调用者。但在EventListener模式的情况下,应用程序的任何其他部分或类都可以注册Button或Checkbox上可能发生的事件。此类类的示例是OtherClass。如果您看到OtherClass的代码,您会发现它已将自己注册为ClickEvent的侦听器,ClickEvent可能发生在“活动”中定义的Button中。有趣的是,除了Activity(调用者)之外,每当Button上发生单击事件时,也会通知这个OtherClass。
其他回答
不透明定义
回调函数是您提供给另一段代码的函数,允许该代码调用它。
有争议的例子
你为什么要这样做?假设您需要调用一个服务。如果服务立即返回,您只需:
叫它吧等待结果结果出来后继续
例如,假设服务是阶乘函数。当您希望值为5!,您将调用factorial(5),并执行以下步骤:
当前执行位置已保存(在堆栈上,但这并不重要)执行移交给阶乘当阶乘完成时,它会将结果放在你可以得到的地方执行恢复到[1]中的位置
现在假设阶乘花费了很长时间,因为你给了它巨大的数字,它需要在一些超级计算集群上运行。假设您预计需要5分钟才能返回结果。您可以:
保持你的设计并在晚上睡觉时运行你的程序,这样你就不会一半时间盯着屏幕设计你的程序,让它在factorial做它的时候做其他的事情
如果您选择第二个选项,则回调可能对您有效。
端到端设计
为了利用回调模式,您需要能够以以下方式调用factorial:
factorial(really_big_number, what_to_do_with_the_result)
第二个参数what_to_do_with_The_result是一个随factorial一起发送的函数,希望factorial在返回之前对其结果进行调用。
是的,这意味着需要编写factorial来支持回调。
现在假设您希望能够向回调传递一个参数。现在你不能,因为你不打算调用它,阶乘是。所以需要编写阶乘来允许你传递参数,当它调用它时,它会将它们传递给回调。它可能看起来像这样:
factorial (number, callback, params)
{
result = number! // i can make up operators in my pseudocode
callback (result, params)
}
现在factorial允许这种模式,回调可能如下所示:
logIt (number, logger)
{
logger.log(number)
}
你对阶乘的要求是
factorial(42, logIt, logger)
如果你想从logIt中返回什么呢?你不能,因为阶乘没有注意到它。
那么,为什么阶乘不能只返回回调返回的值?
使其无阻塞
由于执行是在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)一样小。如果您的语言要求您将其定义为一个单独的函数,并使用完全不必要的名称和签名,那么如果您经常使用这种模式,您的生活将变得不愉快。
另一方面,如果您的语言允许您创建lambda,那么您的状态会更好。然后你会写一些类似的东西
f( func(a) { print(2*a+1); })
这太好了。
如何传递回调
如何将回调函数传递给factorial?嗯,你可以用多种方式来做。
如果被调用的函数在同一进程中运行,则可以传递函数指针或者,您可能希望在程序中维护fn名称-->fn ptr的字典,在这种情况下,您可以传递名称也许您的语言允许您就地定义函数,可能是lambda!在内部,它创建某种对象并传递指针,但您不必担心这一点。也许您正在调用的函数是在一台完全独立的机器上运行的,并且您正在使用HTTP之类的网络协议来调用它。您可以将回调公开为HTTP可调用函数,并传递其URL。
你明白了。
最近出现的回调
在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。我们通常无法控制这些服务,即我们没有编写它们,我们没有维护它们,我们无法确保它们正常运行或如何运行。
但我们不能期望我们的程序在等待这些服务响应时被阻止。意识到这一点,服务提供商通常使用回调模式设计API。
JavaScript非常好地支持回调,例如使用lambdas和闭包。JavaScript世界中有很多活动,无论是在浏览器上还是在服务器上。甚至还有JavaScript平台正在为移动设备开发。
随着我们的前进,我们中越来越多的人将编写异步代码,对此理解至关重要。
回调函数也称为高阶函数,是作为参数传递给另一个函数的函数,回调函数在父函数内调用(或执行)。
$("#button_1").click(function() {
alert("button 1 Clicked");
});
这里我们将一个函数作为参数传递给click方法。click方法将调用(或执行)我们传递给它的回调函数。
这使得回调听起来像方法末尾的返回语句。
我不确定这是什么。
我认为回调实际上是对一个函数的调用,是另一个函数被调用和完成的结果。
我还认为回调是为了处理原始调用,以一种“嘿!你要的东西?我已经做了-我想我会让你知道-还给你”的方式。
我在这个问题上已经迟到了13年,但在我自己学习之后,我想我应该在这里再加一个答案,以防有人像我一样困惑。
其他答案总结了“回调是什么?”这一问题的症结所在
它只是一个函数,在完成某个任务时调用另一个函数。
让我感动的是这样的例子:“你做了这个,现在做了那个。”比如,当我自己可以调用一个方法或函数时,为什么我会这样使用它?
所以这里有一个快速、真实的例子,希望它能让某人“点击”。
超伪码
首先,你会遇到的核心问题。。。。
Multithreaded Method(Some arguments)
{
Do fancy multithreaded stuff....
}
Main()
{
Some stuff I wanna do = some tasks
Multhreaded Method(Some stuff I wanna do)
}
如果在没有任何回调的情况下运行该程序,则程序看起来就像是退出了。因为“花式多线程程序”正在另一个进程上运行。
所以你挠挠头想:“见鬼,我怎么知道什么时候完成?”
繁荣。。。回调
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 tasks
Multhreaded Method(Some stuff I wanna do,Callback)
while(!IsItDone)
Wait a bit
}
这100%不是实现它的最佳方式,我只是想给出一个明确的例子。
所以这并不是简单的“回调是什么?”它是“回调是什么,它有什么好处???”
回调函数是您传递(作为引用或指针)到某个函数或对象的函数。此函数或对象将在以后的任何时候(可能多次)出于任何目的调用此函数:
通知任务结束请求两个项目之间的比较(如在c qsort()中)报告流程的进度通知事件委托对象的初始化授权绘制区域
...
因此,将回调描述为在另一个函数或任务结束时调用的函数过于简单(即使这是一个常见的用例)。