回调函数(callback)是什么?一文理解回调函数(callback)

一口Linux 2024-02-29 11:50

点击左上方蓝色“一口Linux”,选择“设为星标

第一时间看干货文章 

【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式知识点-思维导图-免费获取
【就业】一个可以写到简历的基于Linux物联网综合项目
【就业】找工作简历模版



 1

一、什么是回调函数


1.1、回调函数的定义和基本概念

回调函数是一种特殊的函数,它作为参数传递给另一个函数,并在被调用函数执行完毕后被调用。回调函数通常用于事件处理、异步编程和处理各种操作系统和框架的API。

基本概念:

  1. 回调:指被传入到另一个函数的函数。

  2. 异步编程:指在代码执行时不会阻塞程序运行的方式。

  3. 事件驱动:指程序的执行是由外部事件触发而不是顺序执行的方式。

1.2、回调函数的作用和使用场景

回调函数是一种常见的编程技术,它可以在异步操作完成后调用一个预定义的函数来处理结果。回调函数通常用于处理事件、执行异步操作或响应用户输入等场景。

回调函数的作用是将代码逻辑分离出来,使得代码更加模块化和可维护。使用回调函数可以避免阻塞程序的运行,提高程序的性能和效率。另外,回调函数还可以实现代码的复用,因为它们可以被多个地方调用。

回调函数的使用场景包括:

  1. 事件处理:回调函数可以用于处理各种事件,例如鼠标点击、键盘输入、网络请求等。

  2. 异步操作:回调函数可以用于异步操作,例如读取文件、发送邮件、下载文件等。

  3. 数据处理:回调函数可以用于处理数据,例如对数组进行排序、过滤、映射等。

  4. 插件开发:回调函数可以用于开发插件,例如 WordPress 插件、jQuery 插件等。

回调函数是一种非常灵活和强大的编程技术,可以让我们更好地处理各种异步操作和事件。

二、回调函数的实现方法

回调函数可以通过函数指针或函数对象来实现。

2.1、函数指针

函数指针是一个变量,它存储了一个函数的地址。当将函数指针作为参数传递给另一个函数时,另一个函数就可以使用这个指针来调用该函数。函数指针的定义形式如下:

返回类型 (*函数指针名称)(参数列表)

例如,假设有一个回调函数需要接收两个整数参数并返回一个整数值,可以使用以下方式定义函数指针:

int (*callback)(int, int);

然后,可以将一个实际的函数指针赋值给它,例如:

int add(int a, int b) {
return a + b;
}
callback = add;

现在,可以将这个函数指针传递给其他函数,使得其他函数可以使用这个指针来调用该函数。

2.2、函数对象/functor

除了函数指针,还可以使用函数对象来实现回调函数。函数对象是一个类的实例,其中重载了函数调用运算符 ()。当将一个函数对象作为参数传递给另一个函数时,另一个函数就可以使用这个对象来调用其重载的函数调用运算符。函数对象的定义形式如下:

class callback {
public:
返回类型 operator()(参数列表) {
// 函数体
}
};

例如,假设有一个回调函数需要接收两个整数参数并返回一个整数值,可以使用以下方式定义函数对象:

class Add {
public:
int operator()(int a, int b) {
return a + b;
}
};
Add add;

然后,可以将这个函数对象传递给其他函数,使得其他函数可以使用这个对象来调用其重载的函数调用运算符。

2.3、匿名函数/lambda表达式

回调函数的实现方法有多种,其中一种常见的方式是使用匿名函数/lambda表达式。

Lambda表达式是一个匿名函数,可以作为参数传递给其他函数或对象。在C++11之前,如果想要传递一个函数作为参数,需要使用函数指针或者函数对象。但是这些方法都比较繁琐,需要显式地定义函数或者类,并且代码可读性不高。使用Lambda表达式可以简化这个过程,使得代码更加简洁和易读。

下面是一个使用Lambda表达式实现回调函数的例子:

#include 
#include
#include

void print(int i) {
std::cout << i << " ";
}

void forEach(const std::vector<int>& v, const void(*callback)(int)) {
for(auto i : v) {
callback(i);
}
}

int main() {
std::vector<int> v = {1,2,3,4,5};
forEach(v, [](int i){std::cout << i << " ";});
}

在上面的例子中,我们定义了一个forEach函数,接受一个vector和一个回调函数作为参数。回调函数的类型是void()(int),即一个接受一个整数参数并且返回void的函数指针。在main函数中,我们使用了Lambda表达式来作为回调函数的实现,即[](int i){std::cout << i << " ";}。Lambda表达式的语法为{/ lambda body */},其中[]表示Lambda表达式的捕获列表,即可以在Lambda表达式中访问的外部变量;{}表示Lambda函数体,即Lambda表达式所要执行的代码块。

在使用forEach函数时,我们传递了一个Lambda表达式作为回调函数,用于输出vector中的每个元素。当forEach函数调用回调函数时,实际上是调用Lambda表达式来处理vector中的每个元素。这种方式相比传递函数指针或者函数对象更加简洁和易读。

使用Lambda表达式可以方便地实现回调函数,使得代码更加简洁和易读。但是需要注意Lambda表达式可能会影响代码的性能,因此需要根据具体情况进行评估和选择。

三、回调函数的应用举例

异步编程中的回调函数:网络编程中,当某个连接收到数据后,可以使用回调函数来处理数据。

例如:

void onDataReceived(int socket, char* data, int size);

int main() {
int socket = connectToServer();
startReceivingData(socket, onDataReceived);
// ...
}

void onDataReceived(int socket, char* data, int size) {
// 处理数据...
}

回调函数在GUI编程中的应用:GUI编程中,当用户触发了某个操作时,可以使用回调函数来处理该操作。

例如:

void onButtonClicked(Button* button);

int main() {
Button* button = createButton("Click me");
setButtonClickHandler(button, onButtonClicked);
// ...
}

void onButtonClicked(Button* button) {
// 处理按钮点击事件...
}

事件处理程序中的回调函数:多线程编程中,当某个线程完成了一次任务后,可以使用回调函数来通知主线程。

例如:

void onTaskCompleted(int taskId);

int main() {
for (int i = 0; i < numTasks; i++) {
startBackgroundTask(i, onTaskCompleted);
}
// ...
}

void onTaskCompleted(int taskId) {
// 处理任务完成事件...
}

四、回调函数的优缺点

优点:

  • 提高代码的复用性和灵活性:回调函数可以将一个函数作为参数传递给另一个函数,从而实现模块化编程,提高代码的复用性和灵活性。

  • 解耦合:回调函数可以将不同模块之间的关系解耦,使得代码更易于维护和扩展。

  • 可以异步执行:回调函数可以在异步操作完成后被执行,这样避免了阻塞线程,提高应用程序的效率。

缺点:

  • 回调函数嵌套过多会导致代码难以维护:如果回调函数嵌套层数过多,代码会变得非常复杂,难以维护。

  • 回调函数容易造成竞态条件:如果回调函数中有共享资源访问,容易出现竞态条件,导致程序出错。

  • 代码可读性差:回调函数的使用可能会破坏代码的结构和可读性,尤其是在处理大量数据时。

小结:代码灵活、易于扩展,但是不易于阅读、容易出错。

五、回调函数与其他编程概念的关系

5.1、回调函数和闭包的关系

回调函数和闭包之间存在着紧密的关系。回调函数是一个函数,在另一个函数中被作为参数传递,并在该函数执行完后被调用。闭包是由一个函数及其相关的引用环境组合而成的实体,可以访问函数外部的变量。

在某些情况下,回调函数需要访问到它所在的父函数的变量,这时就需要使用闭包来实现。通过将回调函数放在闭包内部,可以将父函数的变量保存在闭包的引用环境中,使得回调函数能够访问到这些变量。同时,闭包还可以保证父函数中的变量在回调函数执行时不会被销毁,从而确保了回调函数的正确性。

因此,回调函数和闭包是一对密切相关的概念,常常一起使用来实现复杂的逻辑和功能。

5.2、回调函数和Promise的关系

C++回调函数和Promise都是异步编程的实现方式。

回调函数是一种将函数作为参数传递给另一个函数,在异步操作完成后执行的技术。在C++中,回调函数通常使用函数指针或函数对象来实现。当异步操作完成后,会调用注册的回调函数,以便执行相应的处理逻辑。

而Promise则是一种更加高级的异步编程模式,它通过解决回调地狱问题,提供了更加优雅和简洁的异步编程方式。Promise可以将异步操作封装成一个Promise对象,并通过链式调用then()方法来注册回调函数,以及catch()方法来捕获异常。当异步操作完成后,Promise会自动根据操作结果触发相应的回调函数。

因此,可以说C++回调函数和Promise都是异步编程的实现方式,但是Promise提供了更加高级和优雅的编程模式,能够更好地管理异步操作和避免回调地狱问题。

5.3、回调函数和观察者模式的关系

回调函数和观察者模式都是用于实现事件驱动编程的技术。它们之间的关系是,观察者模式是一种设计模式,它通过定义一种一对多的依赖关系,使得一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。而回调函数则是一种编程技术,它允许将一个函数作为参数传递给另一个函数,在执行过程中调用这个函数来完成特定的任务。

在观察者模式中,当一个被观察的对象发生改变时,会遍历所有的观察者对象,调用其定义好的更新方法,以进行相应的操作。这里的更新方法就可以看做是回调函数,因为它是由被观察对象调用的,并且在执行过程中可能需要使用到一些外部参数或上下文信息。因此,可以说观察者模式本身就包含了回调函数的概念,并且借助回调函数来实现观察者模式的具体功能。

六、如何编写高质量的回调函数

回调函数需要遵循以下几个原则:

  1. 明确函数的目的和作用域。回调函数应该有一个清晰的目的,同时只关注与其作用范围相关的任务。

  2. 确定回调函数的参数和返回值。在定义回调函数时,需要明确它所需的参数和返回值类型,这样可以使调用方更容易使用。

  3. 谨慎处理错误和异常。回调函数可能会引发一些异常或错误,需要使用 try-catch 块来处理它们,并给出相应的警告。

  4. 确保回调函数不会导致死锁或阻塞。回调函数需要尽可能快地执行完毕,以避免影响程序的性能和稳定性。

  5. 使用清晰且易于理解的命名规则。回调函数的命名应该清晰、简洁,并尽可能说明其功能和意义。

  6. 编写文档和示例代码。良好的文档和示例代码可以帮助其他开发者更容易地使用回调函数,同时也有助于提高代码的可维护性和可重用性。

  7. 遵循编码规范和最佳实践。 编写高质量的回调函数需要遵守编码规范和最佳实践,例如使用合适的命名规则、注释代码等。

6.1、回调函数的命名规范

回调函数的命名规范没有固定的标准,但是根据通用惯例和编码规范,回调函数的命名应该能够反映函数的作用和功能,让其他开发者能够快速理解并使用。

  1. 使用动词+名词的方式来描述回调函数的作用,例如onSuccess、onError等。

  2. 如果回调函数是用于处理事件的,可以以handleEvent或者onEvent作为函数名。

  3. 如果回调函数是用于处理异步操作完成后的结果,可以以onComplete或者onResult作为函数名。

  4. 在命名时要注意保持简洁明了,不要过于冗长,也不要使用缩写或者不清晰的缩写。

  5. 尽量使用有意义的单词或者短语作为函数名,不要使用无意义的字母或数字组合。

  6. 与代码中其他的函数名称保持一致,尽量避免出现命名冲突的情况。

6.2、回调函数的参数设计

回调函数的参数设计取决于回调函数所需执行的操作和数据。一般来说,回调函数需要接收至少一个参数,通常是处理结果或错误信息。其他可选参数根据需要添加。

例如,如果回调函数是用于处理异步请求的,则第一个参数可能是错误信息(如果存在),第二个参数则是请求返回的数据。另外,也可以将回调函数的上下文传递给该函数作为参数,以便在回调函数中使用。

假设有一个函数 process_data 用于处理数据,但是具体的处理方式需要根据不同的情况进行定制化。这时候我们可以使用回调函数来实现。

回调函数的参数设计如下:

void process_data(void *data, int len, void (*callback)(void *result));

其中,data 表示要处理的数据,len 表示数据的长度,callback 是一个函数指针,用于指定处理完数据后的回调函数。回调函数的形式如下:

void callback_func(void *result);

在 process_data 函数中,首先会对数据进行处理,然后将处理结果传递给回调函数进行处理。具体实现如下:

void process_data(void *data, int len, void (*callback)(void *result)) {
// 处理数据
void *result = data; // 这里只是举个例子,实际上需要根据实际情况进行处理

// 调用回调函数
callback(result);
}

使用示例:

#include 

void callback_func(void *result) {
printf("processing result: %s\n", (char *)result); // 这里只是举个例子,实际上需要根据实际情况进行处理
}

int main() {
char data[] = "hello world";
process_data(data, sizeof(data), callback_func);
return 0;
}

七、总结

回调函数是一种常见的编程模式,主要内容包括以下几个方面:

  • 回调函数的定义:回调函数是一个作为参数传递给其他函数的函数,它能够被异步调用以处理某些事件或完成某些任务。

  • 回调函数的使用场景:回调函数通常用于异步编程中,例如在浏览器端的 AJAX 请求、Node.js 中的文件读写等场景中都会使用回调函数。

  • 回调函数的实现方式:回调函数可以通过直接传入函数名或者通过匿名函数的方式来实现。

  • 回调函数的错误处理:在回调函数中,需要对可能出现的错误进行处理,例如返回错误对象、抛出异常或通过回调函数传递错误信息等方式。

  • 回调函数的优缺点:回调函数可以提高代码的灵活性和可重用性,但也容易导致代码复杂度增加、嵌套过深等问题。

end



一口Linux 


关注,回复【1024】海量Linux资料赠送

精彩文章合集

文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图


一口Linux 写点代码,写点人生!
评论
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 166浏览
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 547浏览
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 204浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 78浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 126浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 108浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 70浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 173浏览
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 187浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 160浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 82浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 221浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦