【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
什么是回调函数?
#1楼
我相信这种“回调”行话在很多地方被错误地使用。 我的定义是这样的:
回调函数是您传递给某人并让他们在某个时间点调用的函数。
我认为人们只是阅读了Wiki定义的第一句话:
回调是对可执行代码或一段可执行代码的引用,该代码作为参数传递给其他代码。
我一直在使用许多API,请参见各种不良示例。 许多人倾向于将函数指针(对可执行代码的引用)或匿名函数(可执行代码的一部分)命名为“回调”,如果它们只是函数,为什么还要为此命名呢?
实际上,Wiki定义中只有第二句话揭示了回调函数和普通函数之间的区别:
这允许较低层的软件层调用较高层中定义的子例程(或函数)。
所以区别在于您将要传递谁,以及传递的函数将如何被调用。 如果仅定义一个函数并将其传递给另一个函数并直接在该函数主体中调用它,则不要将其称为回调。 定义说您传入的函数将被“低级”函数调用。
我希望人们可以在模棱两可的上下文中停止使用此词,它不能帮助人们更好地理解,只会变得更糟。
#2楼
假设我们有一个函数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)` ...
这是在面向对象编程中实现多态的概念
#3楼
比愚蠢的名字callback更好的名字是Call After 。 当一个函数满足条件时,或调用另一个函数,即Call After函数,该函数作为参数接收。
而不是硬编码一个函数中的内部函数,而是编写一个函数以接受已经编写的Call After函数作为参数。 调用之后可能会根据接收参数的函数中的代码检测到的状态更改来调用。
#4楼
回叫最容易用电话系统来描述。 功能呼叫类似于打电话给某人,问一个问题,得到答案并挂断电话。 添加回叫会改变类比,以便在问了她一个问题之后,还给了她您的姓名和电话号码,以便她可以给您回覆答案。
-Paul Jakubik,“ C ++中的回调实现”
#5楼
不透明定义
回调函数是您提供给另一段代码的功能,允许该代码调用该函数。
人为的例子
你为什么想做这个? 假设有一个您需要调用的服务。 如果服务立即返回,则您只需:
- 称它为
- 等待结果
- 结果输入后继续
例如,假设服务是factorial
函数。 当您想要5!
的值时5!
,您将调用factorial(5)
,并且将发生以下步骤:
您当前的执行位置已保存(在堆栈上,但这并不重要)
执行移交给
factorial
当
factorial
完成时,它将结果放在您可以到达的位置执行返回到[1]中的位置
现在,假设factorial
花费了很长时间,因为您要给它提供大量数据,并且它需要在某些超级计算集群中运行。 假设您预计需要5分钟才能返回结果。 你可以:
保持设计并在晚上入睡时运行程序,这样就不会有一半时间盯着屏幕
将程序设计为在
factorial
函数正在执行其操作的同时执行其他操作
如果选择第二个选项,则回调可能对您有用。
端到端设计
为了利用回调模式,您想要的是能够通过以下方式调用factorial
:
factorial(really_big_number, what_to_do_with_the_result)
第二个参数what_to_do_with_the_result
是您发送给factorial
的函数,希望factorial
在返回结果之前对其进行调用。
是的,这意味着需要编写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
的电话是
factorial(42, logIt, logger)
如果您想从logIt
返回什么? 好吧,你不能,因为factorial
没有注意它。
好吧,为什么factorial
不能仅返回回调返回的内容?
使其不阻塞
由于执行是要在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 name --> fn ptr
的字典fn name --> fn ptr
,在这种情况下,您可以传递名称也许您的语言允许您就地定义函数,可能是lambda! 在内部,它正在创建某种对象并传递一个指针,但是您不必为此担心。
也许您正在调用的功能正在完全独立的机器上运行,并且您正在使用诸如HTTP之类的网络协议来调用它。 您可以将回调作为HTTP可调用函数公开,并传递其URL。
你明白了。
近期回调的兴起
在我们进入的这个网络时代,我们调用的服务通常是通过网络进行的。 我们通常对这些服务没有任何控制,即我们没有编写它们,不维护它们,无法确保它们正常运行或性能如何。
但是我们不能期望我们的程序在等待这些服务响应时会阻塞。 意识到这一点,服务提供商经常使用回调模式来设计API。
JavaScript非常好地支持回调,例如使用lambda和闭包。 在JavaScript世界中,浏览器和服务器上都有很多活动。 甚至还有针对移动设备开发的JavaScript平台。
随着我们的前进,越来越多的人将在编写异步代码,对此的理解将至关重要。
来源:oschina
链接:https://my.oschina.net/stackoom/blog/3147850