回调函数是什么?

833

什么是回调函数?


13
您可以在此处找到有关回调函数的最佳解释:https://dev59.com/D2kw5IYBdhLWcg3w2-XH#9652434 - Fakher
5
这是我在YouTube上找到的最好的回调函数解释视频:youtube.com/watch?v=xHneyv38Jro - sss
7
在维基百科上有一篇不简单的解释,可是我们可以更简单地表达:使用回调原理就像给别人一张名片并告诉他:“如果你需要我,请回电话,号码在名片上。” 在编程中,一个函数会将自己的引用留给另一段代码,例如通过注册,其他代码会在适当时候使用这个引用来调用(回调)函数,例如在某个事件发生时。在这种情况下,回调也被称为事件处理程序 - mins
22个回答

18

Call After比愚蠢的名字callback更合适。当或者如果函数内满足条件时,调用另一个接收为参数的函数——Call After函数。

与其在函数内部硬编写内部函数,不如编写一个函数以接收已编写的Call After函数作为参数。基于函数内代码检测到的状态更改,Call After可能会被调用。


这是一个很棒的想法。我采用了“叫作后续调用”来尝试解释这个概念。我可以预见到像马丁·福勒这样的人会在他的经典博客上推广“后续调用”作为这些调用的新术语。 - 8bitjunkie
要是它根本没有名字,那会怎样呢?不是这样最不容易混淆吗?它只是一个函数。 - sheriffderek

18

回调函数是指定给现有函数/方法的一个函数,在某个动作完成、需要额外处理等情况下被调用。

例如,在Javascript中,尤其是在jQuery中,您可以指定回调参数,在动画完成时调用它。

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


12

看这张图片 :)this is how it works

主程序调用库函数(也可能是系统级函数),并传递回调函数名称。这个回调函数可以有多种实现方式。主程序根据需要选择一个回调。

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


7
您是否可以为此添加一个“文字”解释?如果图片消失,这个回答将失去所有的上下文。 - Tim Post
别人的文字解释得最好。唯一觉得缺少的是图片 :) - user1103138
在这里我看到了许多冗长的描述,但这个描述让我恍然大悟,"啊哈,现在我明白它的用途了。" 给你点赞。 - DiBosco
好的,它不必是一个库函数以这种方式回调你。你的程序可以创建一个单独的线程来执行某些任务并触发回调机制,而无需外部库。 - XouDo

7
假设我们有一个函数sort(int *arraytobesorted,void (*algorithmchosen)(void)),它可以接受一个函数指针作为参数,该函数指针可用于在sort()的实现中的某些点上使用。然后,被函数指针algorithmchosen所引用的代码称为回调函数
而且,优势在于我们可以选择任何算法,例如:
  1.    algorithmchosen = bubblesort
  2.    algorithmchosen = heapsort
  3.    algorithmchosen = mergesort   ...

这些原型可能已经被实现,例如:

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

这是面向对象编程中实现多态性的概念。


在javascriptissexy.com/...上有很好的解释,我会在这里转发; 回调函数是作为参数传递给另一个函数的函数,并且回调函数在otherFunction内部被调用或执行。//请注意,click方法参数中的项是一个函数,而不是变量。​ ​//该项是回调函数$("#btn_1").click(function() { alert("Btn 1 Clicked"); });正如您在前面的示例中看到的,我们将一个函数作为参数传递给click方法以便它执行 - - MarcoZen

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

4

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

如果您通过以上精彩答案理解了回调的概念,我建议您应该了解其背后的思想。

“是什么让计算机科学家们开发回调?” 您可能会了解到一个问题,即阻塞(尤其是阻塞UI)。 而回调不是它的唯一解决方案。 还有很多其他解决方案(例如:线程、Futures、Promises...)。


4

在计算机编程中,“回调”是指对可执行代码的引用,或者是一段可执行代码,其作为参数传递给其他代码。这样可以使得一个更低级别的软件层调用在更高级别的层中定义的子例程(或函数)。

使用函数指针在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)();                               
}

如果我们运行这个程序,输出将会是:
这是一个演示函数回调的程序 在注册回调中 在我的回调中 回到主程序里
高层函数以普通调用方式调用低层函数,回调机制允许低层函数通过指向回调函数的指针来调用高层函数。
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个观察者/监听器,某个特定任务已经完成。

  • 列表项

回调机制和EventListener/Observer机制的区别在于,在回调中,被调用方通知单个调用者,而在EventListener/Observer中,被调用方可以通知任何对该事件感兴趣的人(通知可能会去到应用程序的其他部分,而不是触发任务的部分)。

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

事件接口

public interface Events {

public void clickEvent();
public void longClickEvent();
}

组件类 Widget

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.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
}
@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和OnLongClickEventListener。
这两个接口负责监听可能发生在Widget派生的UI组件(例如Button或Checkbox)上的事件。因此,如果将此示例与早期使用Java接口的回调示例进行比较,这两个接口将作为回调接口起作用。因此,高级别代码(此处为Activity)实现了这两个接口。每当小部件发生事件时,都会调用高级别代码(或在高级别代码中实现这些接口的方法,即此处的Activity)。
现在让我讨论回调和事件侦听器模式之间的基本区别。正如我们已经提到的,使用回调,被调用者只能通知单个调用者。但是在事件侦听器模式的情况下,应用程序的任何其他部分或类都可以注册按钮或复选框上可能发生的事件。这种类的示例是OtherClass。如果您查看OtherClass的代码,您会发现它已将自身注册为侦听在Activity中定义的Button上可能发生的ClickEvent。有趣的部分是,除了Activity(调用者)之外,每当单击Button时,OtherClass也将收到通知。

请避免仅提供链接的答案。那些“几乎只是一个指向外部网站的链接”的答案可能会被删除。 - Quentin

3
回调函数是您将其作为引用或指针传递给某个函数或对象的函数。该函数或对象将在以后的任何时间调用此函数,可能会多次调用,以完成以下任何一种目的:
  • 通知任务结束
  • 请求比较两个项目(如C qsort()中)
  • 报告进程进展
  • 通知事件
  • 委派对象的实例化
  • 委派区域的绘制
因此,将回调描述为在另一个函数或任务结束时调用的函数过于简单化(即使这是常见的用例)。

2

虽然我来晚了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
 }

这并不是最好的实现方式,我只是想给出一个清晰的例子。

所以这不仅是简单的“回调是什么?”而是“回调是什么?它对我有什么好处?”


2

一个重要的应用领域是,您可以将您的某个函数注册为句柄(即回调函数),然后发送消息/调用一些函数来完成一些工作或处理。现在,在处理完成后,被调用的函数会调用我们注册的函数(即回调已完成),从而告诉我们处理已完成。
这个维基百科链接通过图形方式很好地解释了回调。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接