如何用简单的英语解释回调?它们与从另一个函数调用一个函数有什么不同?

Yah*_*-Me 334 language-agnostic terminology function callback

如何用简单的英语解释回调?它们与从调用函数中获取某些上下文的另一个函数调用一个函数有什么不同?如何向新手程序员解释他们的权力?

Jos*_*off 518

我将尽力保持这种简单."回调"是由另一个函数调用的任何函数,该函数将第一个函数作为参数.很多的时候,一个"回调"是一个时被调用函数事情发生.在程序员说话中,这个东西可以被称为"事件".

想象一下这种情况:您期待在几天内获得一个包.包裹是给邻居的礼物.因此,一旦你得到包裹,你想要它带到邻居.你不在城里,所以你要为你的配偶留下指示.

您可以告诉他们获取包裹并将其带给邻居.如果你的配偶和计算机一样愚蠢,他们会坐在门口等待包裹直到它来了(没有做任何事情)然后一旦它来了,他们就会把它带到邻居家.但是有更好的方法.告诉你的配偶,一旦他们收到包裹,他们应该把它带到邻居家.然后,他们可以正常生活,直到他们收到包裹.

在我们的例子中,包的接收是"事件",并且将它带到邻居是"回调".您的配偶"运行"您的指示,仅包裹到达将包裹带过来.好多了!

这种想法在日常生活中是显而易见的,但计算机没有同样的常识.考虑一下程序员通常如何写入文件:

fileObject = open(file)
# now that we have WAITED for the file to open, we can write to it
fileObject.write("We are writing to the file.")
# now we can continue doing the other, totally unrelated things our program does
Run Code Online (Sandbox Code Playgroud)

在这里,我们在写入之前等待文件打开.这会"阻止"执行流程,而我们的程序无法执行其可能需要执行的任何其他操作!如果我们能做到这一点怎么办:

# we pass writeToFile (A CALLBACK FUNCTION!) to the open function
fileObject = open(file, writeToFile)
# execution continues flowing -- we don't wait for the file to be opened
# ONCE the file is opened we write to it, but while we wait WE CAN DO OTHER THINGS!
Run Code Online (Sandbox Code Playgroud)

事实证明,我们使用一些语言和框架来做到这一点.它太酷了!查看Node.js以获得这种思考的真实练习.

  • 很好的解释,但我有点困惑.**Callback是多线程的吗?** (19认同)
  • 这是正确的,但不包括回调的所有常见用例.当您需要调用带有参数的函数时,通常会使用回调,这些参数将在另一个函数的过程中处理.例如,在PHP中,array_filter()和array_map()在循环中调用回调. (5认同)
  • 写文件示例是否合适?它似乎对“开放”的工作方式做出了假设。在等待操作系统执行其黑魔法时,`open` 可能会在内部阻塞,这是合理的,在此基础上执行回调。在这种情况下,结果没有区别。 (2认同)
  • 很好的例子!正在到处寻找简单的英语,这是我迄今为止找到的第一个:) (2认同)

Nir*_*nit 113

应用程序通常需要根据其上下文/状态执行不同的功能.为此,我们使用一个变量来存储有关要调用的函数的信息.根据需要,应用程序将使用有关要调用的函数的信息设置此变量,并使用相同的变量调用该函数.

在javascript中,示例如下.这里我们使用方法参数作为变量来存储有关函数的信息.

function processArray(arr, callback) {
    var resultArr = new Array(); 
    for (var i = arr.length-1; i >= 0; i--)
        resultArr[i] = callback(arr[i]);
    return resultArr;
}

var arr = [1, 2, 3, 4];
var arrReturned = processArray(arr, function(arg) {return arg * -1;});
// arrReturned would be [-1, -2, -3, -4]
Run Code Online (Sandbox Code Playgroud)

  • 虽然这在技术上是一个回调,但是给出的解释听起来与通用函数指针不同.这有助于包含可能使用回调的原因. (17认同)
  • 我不明白这个答案.它可以更多地解释而不是代码吗? (4认同)
  • 所以"技巧"是将函数作为参数传递? (2认同)

use*_*994 82

如何用简单的英语解释回调?

简单来说,回调函数就像一个工人,当他完成一个任务时"回调"给他的经理.

它们与从调用函数中获取某些上下文的另一个函数调用一个函数有什么不同?

确实,您正在从另一个函数调用函数,但关键是回调被视为一个Object,因此您可以根据系统状态(如策略设计模式)更改要调用的函数.

如何向新手程序员解释他们的权力?

在需要从服务器提取数据的AJAX风格的网站中可以很容易地看到回调的强大功能.下载新数据可能需要一些时间.如果没有回调,您的整个用户界面将在下载新数据时"冻结",或者您需要刷新整个页面而不仅仅是其中的一部分.使用回调,您可以插入"正在加载"图像,并在加载后将其替换为新数据.

一些没有回调的代码:

function grabAndFreeze() {
    showNowLoading(true);
    var jsondata = getData('http://yourserver.com/data/messages.json');
    /* User Interface 'freezes' while getting data */
    processData(jsondata);
    showNowLoading(false);
    do_other_stuff(); // not called until data fully downloaded
}

function processData(jsondata) { // do something with the data
   var count = jsondata.results ? jsondata.results.length : 0;
   $('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
   $('#results_messages').html(jsondata.results || '(no new messages)');
}
Run Code Online (Sandbox Code Playgroud)

使用回调:

以下是使用jQuery的getJSON进行回调的示例:

function processDataCB(jsondata) { // callback: update UI with results
   showNowLoading(false);
   var count = jsondata.results ? jsondata.results.length : 0;
   $('#counter_messages').text(['Fetched', count, 'new items'].join(' '));
   $('#results_messages').html(jsondata.results || '(no new messages)');
}

function grabAndGo() { // and don't freeze
    showNowLoading(true);
    $('#results_messages').html(now_loading_image);
    $.getJSON("http://yourserver.com/data/messages.json", processDataCB);
    /* Call processDataCB when data is downloaded, no frozen User Interface! */
    do_other_stuff(); // called immediately
}
Run Code Online (Sandbox Code Playgroud)

关闭:

回调通常需要state使用a 来从调用函数访问closure,这就像Worker需要在完成任务之前从Manager获取信息一样.要创建,您可以内联函数,以便它在调用上下文中查看数据:closure

/* Grab messages, chat users, etc by changing dtable. Run callback cb when done.*/
function grab(dtable, cb) { 
    if (null == dtable) { dtable = "messages"; }
    var uiElem = "_" + dtable;
    showNowLoading(true, dtable);
    $('#results' + uiElem).html(now_loading_image);
    $.getJSON("http://yourserver.com/user/"+dtable+".json", cb || function (jsondata) {
       // Using a closure: can "see" dtable argument and uiElem variables above.
       var count = jsondata.results ? jsondata.results.length : 0, 
           counterMsg = ['Fetched', count, 'new', dtable].join(' '),
           // no new chatters/messages/etc
           defaultResultsMsg = ['(no new ', dtable, ')'].join(''); 
       showNowLoading(false, dtable);
       $('#counter' + uiElem).text(counterMsg);
       $('#results'+ uiElem).html(jsondata.results || defaultResultsMsg);
    });
    /* User Interface calls cb when data is downloaded */

    do_other_stuff(); // called immediately
}
Run Code Online (Sandbox Code Playgroud)

用法:

// update results_chatters when chatters.json data is downloaded:
grab("chatters"); 
// update results_messages when messages.json data is downloaded
grab("messages"); 
// call myCallback(jsondata) when "history.json" data is loaded:
grab("history", myCallback); 
Run Code Online (Sandbox Code Playgroud)

关闭

最后,这里是一个定义closure道格拉斯·克罗克福德:

可以在其他函数内定义函数.内部函数可以访问外部函数的变量和参数.如果对内部函数的引用存活(例如,作为回调函数),则外部函数的变量也存活.

也可以看看:

  • +1.第一段是*钱*.然而,其余部分很快就进入了计算机科学术语. (12认同)

小智 39

我惊呆了,看到这么多聪明人没有强调"回调"这个词已经以两种不一致的方式使用的现实.

两种方式都涉及通过将附加功能(函数定义,匿名或命名)传递给现有函数来定制函数.即.

customizableFunc(customFunctionality)
Run Code Online (Sandbox Code Playgroud)

如果只是将自定义功能插入到代码块中,那么您已经自定义了该功能,就像这样.

    customizableFucn(customFunctionality) {
      var data = doSomthing();
      customFunctionality(data);
      ...
    }
Run Code Online (Sandbox Code Playgroud)

虽然这种注入功能通常被称为"回调",但没有任何条件.一个非常明显的例子是forEach方法,其中自定义函数作为参数提供,以应用于数组中的每个元素以修改数组.

但这基本上不同于使用"回调"函数进行异步编程,如AJAX或node.js,或仅仅是为用户交互事件(如鼠标点击)分配功能.在这种情况下,整个想法是在执行自定义功能之前等待偶然事件发生.这在用户交互的情况下是显而易见的,但在可能需要时间的I/O(输入/输出)进程中也很重要,例如从磁盘读取文件.这就是"回调"一词最明显的含义.一旦启动了i/o进程(比如要求从磁盘或服务器读取文件以从http请求返回数据)异步程序不会等待它完成.它可以继续执行接下来安排的任何任务,并且仅在通知读取文件或http请求已完成(或失败)并且数据可用于自定义功能之后才响应自定义功能.这就像打电话给企业并留下你的"回拨号码",这样他们就可以在有人回复你的时候打电话给你.这比谁知道谁知道多久而且无法处理其他事务更好.

异步使用固有地涉及一些监听所需事件的方法(例如,完成I/O过程),以便当它发生时(并且仅当它发生时)执行自定义"回调"功能.在明显的AJAX示例中,当数据实际从服务器到达时,触发"回调"函数以使用该数据来修改DOM,从而将浏览器窗口重绘到该范围.

回顾一下.有些人使用"回调"一词来指代可以作为参数注入现有函数的任何类型的自定义功能.但是,至少在我看来,最合适的用法是将注入的"回调"函数异步使用 - 只有在等待通知的事件发生时才执行.


小智 26

在非程序员的术语中,回调是程序中的空白填充.

许多纸质表格上的一个共同项目是"紧急情况下的人员".那里有一个空白行.你用某人的姓名和电话号码写下来.如果发生紧急情况,那么该人就会被召唤.

  • 每个人都得到相同的空白表格,但是
  • 每个人都可以写一个不同的紧急联系电话.

这是关键.你不要改变形式(代码,通常是别人的代码).但是,您可以填写缺失的信息(您的号码).

例1:

回调用作自定义方法,可能用于添加/更改程序的行为.例如,使用一些执行函数的C代码,但不知道如何打印输出.它所能做的只是制作一个字符串.当它试图找出如何处理字符串时,它会看到一个空行.但是,程序员给你留空写回调!

在此示例中,您不使用铅笔在一张纸上填充空白,而是使用该功能set_print_callback(the_callback).

  • 模块/代码中的空白变量是空白行,
  • set_print_callback 是铅笔,
  • 并且the_callback是您填写的信息.

您现在已在程序中填写此空白行.每当需要打印输出时,它将查看该空行,并按照那里的说明进行操作(即调用放在那里的功能.)实际上,这允许打印到屏幕,日志文件,打印机,通过网络连接或其任何组合.你已经填写了你想做的空白.

例2:

当您被告知需要拨打紧急号码时,您可以阅读纸质表格上的内容,然后拨打您阅读的号码.如果该行为空白,则不会执行任何操作.

Gui编程的工作方式大致相同.单击按钮时,程序需要确定下一步操作.它去寻找回调.这个回调恰好在一个空白处标有"这就是你点击Button1时所做的事情"

当你提出要求时,大多数IDE会自动填写空白(写基本方法)(例如button1_clicked).但是,这个空白可以有任何方法,你请努力.您可以调用该方法,run_computations或者butter_the_biscuits只要将该回调的名称放在正确的空白处即可.您可以将紧急号码中的"555-555-1212"留空.它没有多大意义,但它是允许的.


最后的注意事项:您正在填写回调的空白行?它可以随意擦除和重写.(无论你是否应该是另一个问题,但这是他们权力的一部分)


tov*_*eod 21

程序员Johny需要一台订书机,所以他下到办公室供应部门要求一个,在填写申请表之后,他可以站在那里等待店员去仓库看看订书机(就像一个阻塞功能电话) )或者去做其他的事情.

因为这通常需要时间,所以johny将请求表格与请求表格放在一起,要求他们在订书机准备好接听时给他打电话,同时他可以去做其他事情,比如在桌子上打盹.

  • 承诺只是回调的语法糖. (2认同)

小智 19

总是更好地开始一个例子:).

我们假设您有两个模块A和B.

您希望在模块B中发生某些事件/条件时通知模块A.但是,模块B不知道您的模块A.它只知道通过函数指针(模块A)的特定函数的地址.由模块A提供给它.

所以B现在必须做的是,当使用函数指针发生特定事件/条件时,将"回调"到模块A中.A可以在回调函数内部进行进一步处理.

*)这里的一个明显优势是你从模块B中抽象出模块A的所有内容.模块B不必关心模块A是谁/什么模块.


Bri*_*kel 18

想象一下,你需要一个返回10平方的函数,所以你写一个函数:

function tenSquared() {return 10*10;}
Run Code Online (Sandbox Code Playgroud)

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

function nineSquared() {return 9*9;}
Run Code Online (Sandbox Code Playgroud)

最终,您将使用通用函数替换所有这些:

function square(x) {return x*x;}
Run Code Online (Sandbox Code Playgroud)

完全相同的想法适用于回调.你有一个功能可以执行某些操作,完成后调用doA:

function computeA(){
    ...
    doA(result);
}
Run Code Online (Sandbox Code Playgroud)

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

function computeB(){
    ...
    doB(result);
}
Run Code Online (Sandbox Code Playgroud)

或者你可以将回调函数作为变量传递,只需要有一次函数:

function compute(callback){
    ...
    callback(result);
}
Run Code Online (Sandbox Code Playgroud)

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

除了简化代码之外,它还允许异步代码通过在完成时调用您的任意函数来让您知道它已经完成,类似于您在电话上呼叫某人并留下回拨号码时.


小智 11

你生病了,所以你去看医生.他检查你并确定你需要一些药物.他开了一些药,并将处方药打到你当地的药店.你回家.之后您的药房打电话告诉您,您的处方已经准备就绪.你去拿起它.


Han*_*etz 10

有两点需要解释,一个是回调是如何工作的(传递一个可以在不知道其上下文的情况下调用的函数),另一个是它用于(异步处理事件).

等待包裹到达的类比已被其他答案使用,这是一个很好的解释两者.在计算机程序中,您会告诉计算机需要一个包裹.通常,它现在会坐在那里等待(并且不做任何其他事情),直到包裹到达,如果它永远不会到达,可能会无限期地.对于人类而言,这听起来很愚蠢,但如果没有进一步的措施,这对计算机来说是完全自然的

现在回调将是你前门的钟声.您向包裹服务部门提供了一种方式来通知您包裹的到达,而无需知道您在哪里(即使您)在房子里,或者铃声是如何工作的.(例如,一些"铃声"实际上是在拨打电话.)因为你提供了一个"回叫功能",可以随时"调用",脱离上下文,你现在可以停在前廊并"处理事件"(包裹到达)无论什么时候.


Ada*_*ner 9

现实生活中的例子

这是我自己生活中的一个真实例子。

当我今天下午 5 点完成工作时,我的待办事项清单上有很多事情:

  • 打电话给兽医以获取我的狗的测试结果。
  • 遛狗。
  • 处理我的税款。
  • 洗碗。
  • 回复个人电子邮件。
  • 洗衣服。

当我打电话给兽医时,接待员正在打电话。接待员告诉我,我需要等待兽医来,以便兽医可以向我解释测试结果。接待员想让我等一下,直到兽医准备好。

你对此有何反应?我知道我的:多么低效!所以我向接待员提议让兽医给我回电话在她准备好说话时这样,我就可以处理其他任务,而不必等待电话。然后,当兽医准备好时,我可以暂停其他任务并与她交谈。

它与软件有何关系

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

如果没有回调,当我遇到异步任务时,它就会阻塞。例如。当我打电话给兽医时,兽医需要花大约 15 分钟才能完成她正在做的事情,然后才能与我交谈。如果没有回电,我在这 15 分钟内就会被屏蔽。我只能坐下来等待,而不是去处理其他任务。

以下是没有回调的代码的外观:

function main() {
  callVet();
  // blocked for 15 minutes
  walkDog();
  doTaxes();
  doDishes();
  answerPeronalEmails();
  doLaundry();
}
Run Code Online (Sandbox Code Playgroud)

现在有了回调:

function main() {
  callVet(function vetCallback(vetOnThePhoneReadyToSpeakWithMe) {
    talkToVetAboutTestResults(vetOnThePhoneReadyToSpeakWithMe);
  });
  walkDog();
  doTaxes();
  doDishes();
  answerPeronalEmails();
  doLaundry();
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

现在有了回调:

function main() {
  displayNavbar();
  getUser(function (user) {
    displayUserDashboard(user);
  });
  displaySidebar();
  displayFooter();
}
Run Code Online (Sandbox Code Playgroud)

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


小智 6

想象一下,一位朋友正在离开你的房子,你告诉她"当你回到家时给我打电话,以便我知道你安全到达了"; 这是(字面意思)一个回电.这就是回调函数,无论语言如何.你想要一些程序在完成某项任务时将控制权交还给你,所以你给它一个函数来回调你.

例如,在Python中,

grabDBValue( (lambda x: passValueToGUIWindow(x) ))
Run Code Online (Sandbox Code Playgroud)

grabDBValue可以写入只从数据库中获取一个值,然后让你指定实际对该值做什么,所以它接受一个函数.您不知道何时或是否grabDBValue会返回,但如果/何时返回,您知道您希望它做什么.在这里,我传入一个匿名函数(或lambda),它将值发送到GUI窗口.我可以通过这样做轻松地改变程序的行为:

grabDBValue( (lambda x: passToLogger(x) ))
Run Code Online (Sandbox Code Playgroud)

回调在函数是第一类值的语言中运行良好,就像通常的整数,字符串,布尔值等一样.在C中,你可以通过传递指向它的指针来"传递"一个函数,调用者可以使用它; 在Java中,调用者将要求具有特定方法名称的某种类型的静态类,因为在类之外没有函数("方法",实际上); 在大多数其他动态语言中,您只需使用简单的语法传递函数即可.

专家提示:

在具有词法作用域的语言中(如Scheme或Perl),您可以像这样提出一个技巧:

my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn't need a name, this is for illustration
Run Code Online (Sandbox Code Playgroud)

$val在这种情况下,6因为回调可以访问在词法环境中声明的变量.词汇范围和匿名回调是一个强大的组合,值得进一步研究新手程序员.


And*_*ker 6

您有一些想要运行的代码.通常情况下,当你调用它时,你正在等待它继续运行之前(这可能会导致你的应用程序变灰/产生光标的旋转时间).

另一种方法是并行运行此代码并继续您自己的工作.但是,如果你的原始代码需要根据它调用的代码的响应做不同的事情呢?那么,在这种情况下,你可以传入你希望它在完成后调用的代码的名称/位置.这是一个"回电".

正常代码:询问信息 - >流程信息 - >处理处理结果 - >继续执行其他操作.

回调:询问信息 - >过程信息 - >继续做其他事情.在稍后的某个时候 - >处理Processing的结果.


Luc*_*ano 6

如果没有其他特殊编程资源(如线程等)的回调,程序就是一个接一个地顺序执行的指令序列,甚至是某种条件决定的"动态行为",所有可能的场景应事先编程.

因此,如果我们需要为程序提供真正的动态行为,我们可以使用回调.使用回调,您可以通过参数指示,程序调用另一个程序提供一些先前定义的参数并且可以预期一些结果(这是合同或操作签名),因此这些结果可以由第三方程序生成/处理以前不知道.

这种技术是应用于程序,函数,对象以及计算机运行的所有其他代码的多态性的基础.

当你做一些工作时,用人类世界作为回调的例子是很好的解释,让我们假设你是一个画家(在这里你是主要的程序,画画),并打电话给你的客户有时要求他批准你的工作结果因此,他决定图片是否良好(您的客户是第三方程序).

在上面的例子中,你是一个画家,并"委托"其他人批准结果的工作,图片是参数,每个新客户(回调"功能")改变你的工作决定他想要的结果关于图片(客户做出的决定是"回调函数"的返回结果).

我希望这个解释有用.


ste*_*r25 6

让我们假装你给我一个潜在的长期任务:获得你遇到的前五个独特人物的名字.如果我在人烟稀少的地区,这可能需要几天时间.当我跑来跑去的时候,你真的没有兴趣坐在你的手上,所以你说,"当你拿到名单时,请打电话给我,然后把它读回给我.这是数字."

你给了我一个回调参考 - 我应该执行的一个函数,以便进行进一步的处理.

在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);
Run Code Online (Sandbox Code Playgroud)

这可能会以很多方式得到改善.例如,你可以提供第二个回调:如果它最终花费超过一个小时,请拨打红色电话并告诉那个人你已经超时的答案.


Dej*_*kic 6

根据电话系统最容易描述回叫.功能调用类似于通过电话呼叫某人,向她询问问题,获得答案以及挂断电话; 添加回调更改了类比,以便在向她询问问题后,您还会告诉她您的姓名和号码,以便她可以给您回复答案. - Paul Jakubik,"C++中的回调实现"


som*_*yay 6

\xe2\x80\x9c在计算机编程中,回调是对可执行代码或一段可执行代码的引用,它作为参数传递给其他代码。这允许较低级别的软件层调用较高级别层中定义的子例程(或函数)。\xe2\x80\x9d - 维基百科

\n\n

C 中使用函数指针的回调

\n\n

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

\n\n

例如 int (*ptrFunc) ();

\n\n

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

\n\n

这是一些演示函数指针的代码。

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

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

\n\n

完整的程序包含三个文件:callback.c、reg_callback.h 和 reg_callback.c。

\n\n
/* callback.c */\n#include<stdio.h>\n#include"reg_callback.h"\n\n/* callback function definition goes here */\nvoid my_callback(void)\n{\n    printf("inside my_callback\\n");\n}\n\nint main(void)\n{\n    /* initialize function pointer to\n    my_callback */\n    callback ptr_my_callback=my_callback;                        \n    printf("This is a program demonstrating function callback\\n");\n    /* register our callback function */\n    register_callback(ptr_my_callback);                          \n    printf("back inside main program\\n");\n    return 0;\n}\n\n/* reg_callback.h */\ntypedef void (*callback)(void);\nvoid register_callback(callback ptr_reg_callback);\n\n\n/* reg_callback.c */\n#include<stdio.h>\n#include"reg_callback.h"\n\n/* registration goes here */\nvoid register_callback(callback ptr_reg_callback)\n{\n    printf("inside register_callback\\n");\n    /* calling our callback function my_callback */\n    (*ptr_reg_callback)();                               \n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

这是一个演示主程序内函数回调\内部register_callback\内部my_callback\nback的程序

\n\n

上层函数调用下层函数就像普通调用一样,回调机制允许下层函数通过指向回调函数的指针来调用上层函数。

\n\n

Java中使用接口回调

\n\n

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

\n\n

让我通过一个例子来演示一下:

\n\n

回调接口

\n\n
public interface Callback\n{\n    public void notify(Result result);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

调用者或更高级别的类

\n\n
public Class Caller implements Callback\n{\nCallee ce = new Callee(this); //pass self to the callee\n\n//Other functionality\n//Call the Asynctask\nce.doAsynctask();\n\npublic void notify(Result result){\n//Got the result after the callee has finished the task\n//Can do whatever i want with the result\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

被调用者或下层函数

\n\n
public Class Callee {\nCallback cb;\nCallee(Callback cb){\nthis.cb = cb;\n}\n\ndoAsynctask(){\n//do the long running task\n//get the result\ncb.notify(result);//after the task is completed, notify the caller\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用事件监听器模式进行回调

\n\n
    \n
  • 项目清单
  • \n
\n\n

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

\n\n
    \n
  • 项目清单
  • \n
\n\n

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

\n\n

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

\n\n

事件接口

\n\n
public interface Events {\n\npublic void clickEvent();\npublic void longClickEvent();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

类小部件

\n\n
package com.som_itsolutions.training.java.exampleeventlistener;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\n\npublic class Widget implements Events{\n\n    ArrayList<OnClickEventListener> mClickEventListener = new ArrayList<OnClickEventListener>(); \n    ArrayList<OnLongClickEventListener> mLongClickEventListener = new ArrayList<OnLongClickEventListener>();\n\n    @Override\n    public void clickEvent() {\n        // TODO Auto-generated method stub\n        Iterator<OnClickEventListener> it = mClickEventListener.iterator();\n                while(it.hasNext()){\n                    OnClickEventListener li = it.next();\n                    li.onClick(this);\n                }   \n    }\n    @Override\n    public void longClickEvent() {\n        // TODO Auto-generated method stub\n        Iterator<OnLongClickEventListener> it = mLongClickEventListener.iterator();\n        while(it.hasNext()){\n            OnLongClickEventListener li = it.next();\n            li.onLongClick(this);\n        }\n\n    }\n\n    public interface OnClickEventListener\n    {\n        public void onClick (Widget source);\n    }\n\n    public interface OnLongClickEventListener\n    {\n        public void onLongClick (Widget source);\n    }\n\n    public void setOnClickEventListner(OnClickEventListener li){\n        mClickEventListener.add(li);\n    }\n    public void setOnLongClickEventListner(OnLongClickEventListener li){\n        mLongClickEventListener.add(li);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

班级按钮

\n\n
public class Button extends Widget{\nprivate String mButtonText;\npublic Button (){\n} \npublic String getButtonText() {\nreturn mButtonText;\n}\npublic void setButtonText(String buttonText) {\nthis.mButtonText = buttonText;\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

类复选框

\n\n
public class CheckBox extends Widget{\nprivate boolean checked;\npublic CheckBox() {\nchecked = false;\n}\npublic boolean isChecked(){\nreturn (checked == true);\n}\npublic void setCheck(boolean checked){\nthis.checked = checked;\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

活动课

\n\n

包 com.som_itsolutions.training.java.exampleeventlistener;

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

其他类

\n\n
public class OtherClass implements Widget.OnClickEventListener{\nButton mButton;\npublic OtherClass(){\nmButton = Activity.getActivityHandle().mButton;\nmButton.setOnClickEventListner(this);//interested in the click event                        //of the button\n}\n@Override\npublic void onClick(Widget source) {\nif(source == mButton){\nSystem.out.println("Other Class has also received the event notification...");\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

主班

\n\n
public class Main {\npublic static void main(String[] args) {\n// TODO Auto-generated method stub\nActivity a = new Activity();\nOtherClass o = new OtherClass();\na.doSomeWork(a.mButton);\na.doSomeWork(a.mCheckBox);\n}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

这两个接口负责监听 Widget 派生 UI 组件(如 Button 或 Checkbox)上可能发生的事件。因此,如果我们将此示例与之前使用 Java 接口的 Callback 示例进行比较,就会发现这两个接口充当 Callback 接口。所以更高层的代码(这里是Activity)实现了这两个接口。而每当一个widget发生事件时,高层代码(或者高层代码中实现的这些接口的方法,这里就是Activity)就会被调用。

\n\n

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

\n


Nis*_*ant 5

通常我们将变量发送给函数.假设你有一个任务需要在作为参数给出之前处理变量 - 你可以使用回调.

function1(var1, var2) 是通常的方式.

如果我想要var2处理然后作为参数发送怎么办? function1(var1, function2(var2))

这是一种类型的回调 - function2执行一些代码并将变量返回给初始函数.

  • 什么是另一种回调? (2认同)

Gul*_*han 5

为了教授回调,你必须先教授指针。一旦学生理解了指向变量的指针的概念,回调的概念就会变得更容易。假设您使用的是 C/C++,则可以遵循这些步骤。

  • 首先向您的学生展示如何使用指针以及使用普通变量标识符来使用和操作变量。
  • 然后教他们有些事情只能用指针来完成(比如通过引用传递变量)。
  • 然后告诉他们可执行代码或函数与内存中的其他一些数据(或变量)有何相似之处。所以,函数也有地址或指针。
  • 然后向他们展示如何使用函数指针调用函数并告诉他们这些被称为回调。
  • 现在,问题是,为什么调用某些函数会如此麻烦?有什么好处?像数据指针一样,函数指针又名回调比使用普通标识符有一些优势。
  • 第一个是,函数标识符或函数名称不能用作普通数据。我的意思是,你不能用函数(如数组或函数链表)创建数据结构。但是通过回调,您可以创建一个数组、一个链表或将它们与其他数据一起使用,例如键值对或树的字典或任何其他内容。这是一个强大的好处。其他好处实际上是这个好处的孩子。
  • 在事件驱动程序编程中可以看到回调的最常见用途。其中一个或多个功能是根据一些输入信号执行的。通过回调,可以维护一个字典来映射带有回调的信号。那么输入信号的解析和相应代码的执行就变得容易多了。
  • 我想到的回调的第二个用途是高阶函数。将其他函数作为输入参数的函数。要将函数作为参数发送,我们需要回调。一个例子可以是一个接受数组和回调的函数。然后它对数组的每个项目执行回调并将结果返回到另一个数组中。如果我们向函数传递一个加倍回调,我们会得到一个加倍值的数组。如果我们传递一个平方回调,我们会得到平方。对于平方根,只需发送适当的回调。这是普通函数无法做到的。

可能还有很多东西。让学生参与,他们会发现。希望这可以帮助。


Dav*_*res 5

回调是一个将由第二个函数调用的函数.第二个函数事先不知道它将调用什么函数.因此,回调函数的标识存储在某处,或作为参数传递给第二个函数.这种"身份",取决于编程语言,可能是回调的地址,或某种其他类型的指针,或者它可能是函数的名称.主体是相同的,我们存储或传递一些明确标识功能的信息.

到时,第二个函数可以调用回调,根据当时的情况提供参数.它甚至可能从一组可能的回调中选择回调.编程语言必须提供某种语法,以允许第二个函数调用回调,知道它的"身份".

这种机制有很多可能的用途.通过回调,函数的设计者可以通过调用所提供的任何回调来对其进行自定义.例如,排序函数可能将回调作为参数,并且此回调可能是用于比较两个元素以确定哪个元素首先出现的函数.

顺便说一句,根据编程语言,上面讨论中的"函数"一词可能被"块","闭包","lambda"等替换.