回调函数的概念、意义和应用场景

devtools/2024/11/17 0:28:55/

概念

回调函数,就是使用者自己定义一个函数,并实现函数的内容,然后把这个函数作为参数传入其它函数中,由其它函数在运行时来调用。
换句话说,函数是你实现的,但由别人的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。

为什么要用回调函数?

这是一种设计策略。我们想象一种系统实现:
在一个下载系统中,有一个文件下载模块、一个下载文件当前进度显示模块,系统要求实时显示文件的下载进度,想想很简单,在面向对象的世界里,无非是实现两个类而已。
但是问题恰恰出在这里。显示模块如何驱动下载进度条?
文件下载进度只有下载模块才知道。显示模块不知道,也不应该知道下载模块的进度,这是面向对象“高内聚、低耦合”的设计特性决定的。
解决方案很简单:
给下载模块传递一个函数指针,作为回调函数,驱动显示模块的显示进度。
在面向对象的世界中这样的例子还真不少。
造成这样问题的根源,就是面向对象的程序设计思想,要求模块独立性、高内聚低耦合等特性。

回调函数机制

  • 定义一个函数(普通函数即可);
  • 将此函数的地址注册给调用者;
  • 特定的事件或条件发生时,调用者使用函数指针调用回调函数。

不带参数的回调函数

一个标准Hello World程序如下:

int main(int argc,char* argv[]){printf("Hello World!\n");return 0;
}

将它修改成函数回调样式:

//定义回调函数
void PrintfText() {printf("Hello World!\n");
}//定义实现回调函数的"调用函数"
void CallPrintfText(void (*callfuct)()){callfuct();
}//在main函数中实现函数回调
int main(int argc,char* argv[]){CallPrintfText(PrintfText);return 0;
}

带参数的回调函数

//定义带参回调函数
void PrintfText(char* s) {printf(s);
}//定义实现带参回调函数的"调用函数"
void CallPrintfText(void (*callfuct)(char*), char* s){callfuct(s);
}//在main函数中实现带参的函数回调
int main(int argc,char* argv[]){CallPrintfText(PrintfText,"Hello World!\n");return 0;
}

应用场景

扩展库

假设有这样一种情况:
我们要编写一个库,它需要排序算法(如冒泡排序、快速排序、shell排序、shake排序等等),为了能让库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑。
此时,就可以把排序算法定义成回调函数,由库来调用。

定时通知机制

例如,在A模块中设置一个计时器,定期调用B模块提供的回调函数,这就是一种通知机制。

  • 编写好一个定时器模块,到了时间就触发一次调用。
  • 同样地,提供1)回调函数定义;2)注册回调函数接口。
  • 应用者把自己需要定时处理的过程编写成一个实际函数,需要满足回调函数的定义,注册到定时器。

底层模块调用高层模块,如驱动

底层模块调用高层模块,但高层模块还没写好,或者说,底层模块根本不需要知道高层模块是怎么编写的,这个时候用回调函数是最好的。
如,驱动收到数据后,调用高层应用处理,就属于这种场景。

  • 驱动层提供1)回调函数定义,2)注册回调函数的接口。
  • 应用层1)编写回调函数的实现;2)调用驱动层的注册接口将实现的回调函数指针注册给驱动层。
  • 当驱动层收到数据后,调用所注册的回调函数,处理数据。

总结

如果一个对象关心另一个对象的状态变化,那么,给状态的变化注册回调函数,让它通知你这类状态的改变,这样,在封装了模块变化的同时,实现了模块间的协作关系,另辟独径地给对象之间进行了解耦。


http://www.ppmy.cn/devtools/134573.html

相关文章

flutter下拉刷新上拉加载的简单实现方式三

使用 CustomScrollView 结合 SliverList 实现了一个支持下拉刷新和上拉加载更多功能的滚动列表,对下面代码进行解析学习。 import dart:math;import package:flutter/material.dart;import custom_pull/gsy_refresh_sliver.dart; import package:flutter/cupertino…

hhdb数据库介绍(9-14)

SQL语法支持 DML语句 在关系集群数据库中,DML语句的逻辑将变的更为复杂。计算节点将DML语句分为两大类:单库DML语句与跨库DML语句。 单库DML语句,指SQL语句只需在一个节点上运行,即可计算出正确结果。假设分片表customer分片字…

杨中科 .Net Core 笔记 DI 依赖注入2

ServiceCollection services new ServiceCollection();//定义一个承放服务的集合 services.AddScoped<iGetRole, GetRole>();using (ServiceProvider serviceProvider services.BuildServiceProvider()) {var list serviceProvider.GetServices(typeof(iGetRole));//获…

力扣题目解析--合并两个链表

题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#xff…

Java基础:内部类

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

密码学在网络安全中的应用

密码学作为网络安全领域的核心技术之一&#xff0c;发挥着举足轻重的作用。以下是对密码学在网络安全中应用的详细阐述&#xff1a; 一、数据加密密码学通过加密算法将明文转换为密文&#xff0c;以防止未经授权的个人或机构获取敏感信息。这主要包括&#xff1a;对称加密&…

正点原子IMX6ULL--嵌入式Linux开发板学习中常用命令和笔记记录

学习路线图 传驱动文件 sudo cp chrdevbase.ko chrdevbaseApp /home/txj/linux/nfs/rootfs/lib/modules/4.1.15/ -f bootcmd setenv bootcmd tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000 setenv bootcmd tftp 80800000 zImag…

PHP大模型深度学习库TransformersPHP 安装体验

TransformersPHP是一个工具包&#xff0c;PHP开发人员可以轻松地将机器学习魔法添加到他们的项目中。 管方地址&#xff1a;TransformersPHP github地址&#xff1a;GitHub - CodeWithKyrian/transformers-php: Transformers PHP is a toolkit for PHP developers to add machi…