如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?


当前回答

什么是回调函数?

对第一个问题的简单回答是,回调函数是通过函数指针调用的函数。如果你将一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,就被称为回调。

回调函数很难跟踪,但有时它非常有用。尤其是在设计库的时候。回调函数就像让你的用户给你一个函数名,你会在一定条件下调用这个函数。

例如,您编写了一个回调计时器。它允许您指定持续时间和调用什么函数,函数将相应地被回调。“每10秒运行myfunction() 5次”

或者,您可以创建一个函数目录,传递一个函数名列表,并要求库进行相应的回调。"如果成功,回调成功(),如果失败,回调失败()。"

让我们看一个简单的函数指针的例子

void cbfunc()
{
     printf("called");
}

 int main ()
 {
                   /* function pointer */ 
      void (*callback)(void); 
                   /* point to your callback function */ 
      callback=(void *)cbfunc; 
                   /* perform callback */
      callback();
      return 0; 
}

如何传递参数回调函数?

注意到实现回调的函数指针接受void *,这表明它可以接受任何类型的变量,包括结构。因此,您可以通过结构传递多个参数。

typedef struct myst
{
     int a;
     char b[10];
}myst;

void cbfunc(myst *mt) 
{
     fprintf(stdout,"called %d %s.",mt->a,mt->b); 
}

int main() 
{
       /* func pointer */
    void (*callback)(void *);       //param
     myst m;
     m.a=10;
     strcpy(m.b,"123");       
     callback = (void*)cbfunc;    /* point to callback function */
     callback(&m);                /* perform callback and pass in the param */
     return 0;   
}

其他回答

回调函数是作为参数传递给另一个函数的函数(在某些时候使用)。

以下是一些函数:

def greeting(name):
    print("Hello " + name + "!")

def departing(name):
    print("Goodbye " + name + "!")

下面是一个函数(使用ourCallBack作为回调参数):

def promptForName(ourCallback):
    myName = input("Enter Name:")
    ourCallback(myName)

现在让我们使用一些回调!

promptForName(greeting) 
# Enter Name: 
# >Ed
# Hello Ed!

promptForName(departing) 
# Enter Name: 
# >Ed
# Goodbye Ed!

promptForName(greeting) 
# Enter Name: 
# >Guy
# Hello Guy!

我能够很快地扩展我的代码。


处理(错误和误导性的)答案:

回调并不意味着异步!

JS在2015年得到承诺,async/await在2017年得到承诺。在此之前,使用回调。

这就是为什么这里的一些答案没有意义,他们把两者混为一谈了!

它们通常用于异步代码,但我的示例是同步的。

回调并不意味着事件驱动!

它们通常用于事件处理,但我的示例不是事件。

回调并不意味着闭包!

虽然通常用作提供闭包的一种简洁方式,但我的示例并不是这样。

回调不是第一类函数的完整定义!

它是创建第一类函数定义的众多特性之一。

C语言可以使用函数指针作为回调函数,尽管它没有第一类函数。

假设你需要一个函数返回10的平方,那么你写一个函数:

function tenSquared() {return 10*10;}

之后你需要9的平方,所以你写了另一个函数:

function nineSquared() {return 9*9;}

最终你将用一个泛型函数替换所有这些:

function square(x) {return x*x;}

同样的想法也适用于回调。你有一个函数,它做一些事情,当完成时调用doA:

function computeA(){
    ...
    doA(result);
}

之后你想要完全相同的函数调用doB,而不是你可以复制整个函数:

function computeB(){
    ...
    doB(result);
}

或者你可以将回调函数作为变量传递,并且只需要使用该函数一次:

function compute(callback){
    ...
    callback(result);
}

然后你只需要调用compute(doA)和compute(doB)。

除了简化代码之外,它还让异步代码通过在完成时调用任意函数来让您知道它已经完成,这与打电话给某人并留下回调号码类似。

让我们假设您要给我一个可能长期运行的任务:获取您遇到的前五个独特的人的名字。如果我在人口稀少的地区,这可能需要几天的时间。你真的不想在我忙来忙去的时候袖手旁观,所以你说:“你拿到名单后,打我手机给我读一遍。这是号码。”

您已经给了我一个回调引用——一个我应该执行以传递进一步处理的函数。

在JavaScript中,它可能是这样的:

var lottoNumbers = [];
var callback = function(theNames) {
  for (var i=0; i<theNames.length; i++) {
    lottoNumbers.push(theNames[i].length);
  }
};

db.executeQuery("SELECT name " +
                "FROM tblEveryOneInTheWholeWorld " +
                "ORDER BY proximity DESC " +
                "LIMIT 5", callback);

while (lottoNumbers.length < 5) {
  playGolf();
}
playLotto(lottoNumbers);

这可能有很多方面可以改进。例如,你可以提供第二次回拨:如果最终超过一个小时,打电话给红色电话,告诉接听电话的人你超时了。

现实生活中的例子

这里有一个真实的例子,嗯,我自己的生活。

当我下午5点结束我的工作时,我的待办事项清单上有很多事情:

打电话给兽医要我的狗的检查结果。 遛狗。 处理我的税务问题。 洗碗。 回复私人邮件。 洗衣服。

当我打电话给兽医时,我接到了一个接待员的电话。接待员告诉我,我需要等待兽医,这样兽医就可以向我解释测试结果。接待员想让我等一下,直到兽医准备好。

你对此有什么反应?我知道我的工作多么低效!所以我向接待员提议,当兽医准备好谈话时,他让兽医给我回个电话。这样,我就不用等电话了,我可以做其他的事情。等兽医准备好了,我就可以把其他的事情暂时搁置,和她谈谈。

它和软件有什么关系

我是单螺纹的。我一次只能做一件事。如果我是多线程的,我将能够并行处理多个任务,但不幸的是,我不能这样做。

如果回调不是一个东西,当我遇到异步任务时,它会阻塞。如。当我打电话给兽医时,兽医需要大约15分钟来完成她正在做的事情,然后她才能和我说话。如果没有回调,我在这15分钟内就会被屏蔽。我就只能坐着等,而不能做其他的工作。

下面是没有回调的代码的样子:

function main() {
  callVet();
  // blocked for 15 minutes
  walkDog();
  doTaxes();
  doDishes();
  answerPeronalEmails();
  doLaundry();
}

现在用回调:

function main() {
  callVet(function vetCallback(vetOnThePhoneReadyToSpeakWithMe) {
    talkToVetAboutTestResults(vetOnThePhoneReadyToSpeakWithMe);
  });
  walkDog();
  doTaxes();
  doDishes();
  answerPeronalEmails();
  doLaundry();
}

更一般地说,当您处于单线程执行环境中,并且有某种异步任务时,您可以使用回调以更合乎逻辑的顺序执行事情,而不是让该任务阻塞您的单线程。

一个很好的例子是,如果您有一些前端代码需要发出ajax请求。如。如果您有一个显示用户信息的仪表板。下面是它如何在没有回调的情况下工作。用户将立即看到导航栏,但他们必须等待一段时间才能看到边栏和页脚,因为ajax请求getUser需要一段时间(作为经验法则,网络被认为是很慢的)。

function main() {
  displayNavbar();
  const user = getUser();
  // wait a few seconds for response...
  displayUserDashboard(user);
  displaySidebar();
  displayFooter();
}

现在用回调:

function main() {
  displayNavbar();
  getUser(function (user) {
    displayUserDashboard(user);
  });
  displaySidebar();
  displayFooter();
}

通过利用回调,我们现在可以在ajax请求的响应返回给我们之前显示边栏和页脚。这就好比我对接待员说:“我不想在电话上等15分钟。兽医准备好和我谈谈的时候给我回电话,与此同时,我会继续做我待办事项清单上的其他事情。”在现实生活中,你可能应该更优雅一些,但在编写软件时,你可以对CPU非常粗鲁。

用电话系统来描述回调是最容易的。函数调用类似于打电话给某人,问她一个问题,得到答案,然后挂断电话;添加回调改变了这个类比,这样在问她一个问题之后,你也可以告诉她你的名字和电话号码,这样她就可以给你回电话,告诉你答案。——保罗·雅库比 、《c++中的回调实现》