如何用简单的英语解释回调?它们与从一个函数调用另一个函数从调用函数获取上下文有什么不同?如何向新手程序员解释它们的强大功能?
当前回答
什么是回调函数?
对第一个问题的简单回答是,回调函数是通过函数指针调用的函数。如果你将一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,就被称为回调。
回调函数很难跟踪,但有时它非常有用。尤其是在设计库的时候。回调函数就像让你的用户给你一个函数名,你会在一定条件下调用这个函数。
例如,您编写了一个回调计时器。它允许您指定持续时间和调用什么函数,函数将相应地被回调。“每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;
}
其他回答
假设你需要一个函数返回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)。
除了简化代码之外,它还让异步代码通过在完成时调用任意函数来让您知道它已经完成,这与打电话给某人并留下回调号码类似。
通常,我们将变量发送给函数:function1(var1, var2)。
假设,你想在它被作为参数给出之前处理它:
这是一种回调类型,其中function2执行一些代码并将变量返回给初始函数。
编辑:回调这个词最常见的意思是一个函数作为参数传递给另一个函数,并在稍后的时间点被调用。这些思想存在于允许高阶函数的语言中,即将函数视为一等公民,通常用于异步编程中。onready dosomething()。在这里,只有当它准备好了,事情才会发生。
在非程序员的术语中,回调是程序中的填空。
许多纸质表格上常见的一项是“紧急情况下的电话号码”。这里有一个空行。你写上某人的名字和电话号码。如果发生紧急情况,就会打电话给那个人。
每个人都有相同的空白表格,但是 每个人都可以写不同的紧急联系电话。
这是关键。您不需要更改表单(代码,通常是其他人的)。不过,你可以填写缺失的信息(你的电话号码)。
示例1:
回调被用作自定义方法,可能用于添加/更改程序的行为。例如,一些C代码执行一个函数,但不知道如何打印输出。它所能做的就是创造一个字符串。当它试图弄清楚该如何处理字符串时,它看到了一个空行。但是,程序员给了你写回调的空白!
在这个例子中,你不用铅笔在纸上填空白,你使用函数set_print_callback(the_callback)。
模块/代码中的空白变量是空行, Set_print_callback是铅笔, the_callback是你要填写的信息。
现在您已经在程序中填充了这一行空白。当它需要打印输出时,它将查看空白行,并遵循那里的指示(即调用您放在那里的函数)。实际上,这允许打印到屏幕、日志文件、打印机、通过网络连接或它们的任何组合。你已经填满了你想做的事情。
示例2:
当你被告知需要拨打一个紧急电话号码时,你去阅读纸质表格上写的内容,然后拨打你读到的号码。如果这一行是空的,什么也不会做。
Gui编程的工作原理与此大致相同。当一个按钮被点击时,程序需要弄清楚下一步要做什么。它去寻找回调。这个回调恰好在一个空白中,标签是"点击Button1时你所做的事情"
大多数ide会在你要求它(例如button1_clicked)时自动为你填充空白(编写基本方法)。不过那个空白可以有任何方法你都可以补好。你可以调用方法run_computices或butter_the_biscuits,只要你把那个回调的名字放在适当的空格里。你可以把“555-555-1212”写在紧急号码栏里。这没什么意义,但这是允许的。
最后提示:用回调填充的空行?它可以被随意擦除和重写。(你是否应该是另一个问题,但这是他们权力的一部分)
在PHP中,它是这样的:
<?php
function string($string, $callback) {
$results = array(
'upper' => strtoupper($string),
'lower' => strtolower($string),
);
if(is_callable($callback)) {
call_user_func($callback, $results);
}
}
string('Alex', function($name) {
echo $name['lower'];
});
从一个例子开始总是更好的:)。
假设有两个模块A和B。
你希望模块A在模块B中发生某些事件/条件时得到通知。然而,模块B对模块A一无所知。它只知道模块A提供给它的函数指针指向模块A的特定函数的地址。
因此,所有B现在必须做的是,当一个特定的事件/条件发生时,使用函数指针“回调”到模块A。A可以在回调函数内部进行进一步处理。
这里一个明显的优点是,你从模块B中抽象出了模块A的所有内容。模块B不必关心模块A是谁/什么。