


























回调函数(Callback Function) 其实一点也不神秘。用一句话概括:它是一个“现在先定义好,留着以后让别人来调用”的函数。
为了让你彻底明白,我们不用复杂的底层术语,而是用一个生活中的类比、一张图和一段简单的代码来解释。
在计算机里,“外卖小哥”就是系统或者框架,“你的电话和吃饭动作”就是回调函数。
通常的程序是由你写代码去调用系统的 API;而回调函数正好相反,是系统在特定的时候,反过来调用你写的代码。所以它也经常被称为“好莱坞原则”:Don't call us, we'll call you.(别打给我们,我们会打给你。)
回到你之前看到的 Nana 库的例子。这就是一个完美的、典型的回调函数应用场景:
// 1. 这是一个回调函数(你定义好了它,但你现在不执行它)
void clicked()
{
std::cout << "按钮被点击了!";
}
int main()
{
nana::form fm;
// 2. 注册回调函数:你把这个函数“交给”了 Nana 框架
// 你告诉 Nana:“一旦这个窗口被点击了,你就去调用 clicked 这个函数”
fm.events().click(clicked);
// 3. 进入主循环。此时程序在等待用户的动作
nana::exec();
}
在这里:
main 函数里主动写 clicked();。clicked 何时运行,取决于用户什么时候把鼠标挪过去并按下左键。clicked()**。如果没有回调函数,写代码会变得非常痛苦。
想象一下,如果我们要检测按钮是否被点击,在没有回调函数的时代,你必须写一个死循环(while(true))去不停地询问操作系统:“用户点了吗?点了吗?点了吗?”。这不仅极其浪费 CPU 性能,还会让整个程序卡死。
而有了回调函数:
系统或框架之所以能知道并判断这是一个“回调函数”,并没有什么玄乎的魔法,全靠你和框架之间达成的一个“契约”。
在底层,这个契约是通过类型系统(Type System)和内存地址(指针)来实现的。以下是系统框架识别并处理回调函数的完整内部过程:
在 Nana 或任何其他框架的源码里,设计者早就提前写好了一个“接收器”。这个接收器规定了:我这里可以保存一个具有特定特征的函数。
在 C++ 中,这个接收器通常是 std::function 或者一个模板。
比如 Nana 的底层类似于这样写的:
namespace nana {
class event_manager {
public:
// 框架提前挖好了一个“坑”(变量名叫做 callback_store)
// 它规定:只要是“没有参数且没有返回值”的函数,都可以填进这个坑里
std::function<void()> callback_store;
// 提供一个接口,让用户把函数传进来
void click(std::function<void()> user_function) {
callback_store = user_function; // 框架把你的函数“记住”了
}
};
}
当你调用 fm.events().click(clicked); 时,编译器会去匹配类型。如果你的函数满足框架要求的特征(比如没有参数、没有返回值),编译器就会放行,框架就会把你的函数的内存地址记录在自己的变量里。
系统是怎么判断并调用它的呢?这里有一个完整的链条:
nana::exec() 消息循环。// 框架底层的伪代码
void OnWindowMessage(Message msg) {
if (msg.type == MOUSE_CLICK) {
// 检查用户之前有没有通过 click() 注册过回调函数
if (this->callback_store != nullptr) {
// 找到了!框架立刻通过记录的内存地址去执行这个函数
this->callback_store();
}
}
}
对系统和框架来说,它其实不需要去专门判断“这是一个回调函数”。在框架眼里,你传过来的 clicked 函数本质上和一串数字(内存地址)没有区别。
clicked(),CPU 直接跳转到那个地址执行。clicked 的地址当成参数传给了框架。框架把这个地址存进了它的成员变量里。等到满足特定条件(如检测到点击)时,框架的内部代码再去访问这个变量,并跳转到该地址执行。系统能识别回调函数,是因为:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。