C语言--回调函数

news2024/9/21 16:41:55

1. 什么是回调函数?

回调函数,光听名字就比普通函数要高大上一些,那到底什么是回调函数呢?恕我读得书少,没有在那本书上看到关于回调函数的定义。我在百度上搜了一下,发现众说纷纭,有很大一部分都是使用类似这么一个场景来说明:A君去B君店里买东西,恰好缺货,A君留下号码给B君,有货时通知A君。感觉这个让人更容易想到的是异步操作,而不是回调。另外还有两句英文让我印象深刻:1) If you call me, I will call you back; 2) Don’t call me, I will call you. 看起来好像很有道理,但是仔细一想,普通函数不也可以做到这两点吗?所以,我觉得这样的说法都不是很妥当,因为我觉得这些说法都没有把回调函数的特点表达出来,也就是都看不到和普通函数到底有什么差别。不过,百度百科的解析我觉得还算不错(虽然经常吐槽百度搜索…):回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

下面先说说我的看法。我们可以先在字面上先做个分解,对于"回调函数",中文其实可以理解为这么两种意思:1) 被回调的函数;2) 回头执行调用动作的函数。那这个回头调用又是什么鬼?

先来看看来自维基百科的对回调(Callback)的解析:In computer programming, a callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback. 也就是说,把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称之为异步回调。关于同步和异步,这里不作讨论,请查阅相关资料。

再来看看来自Stack Overflow某位大神简洁明了的表述:A “callback” is any function that is called by another function which takes the first function as a parameter。 也就是说,函数 F1 调用函数 F2 的时候,函数 F1 通过参数给 函数 F2 传递了另外一个函数 F3 的指针,在函数 F2 执行的过程中,函数F2 调用了函数 F3,这个动作就叫做回调(Callback),而先被当做指针传入、后面又被回调的函数 F3 就是回调函数。到此应该明白回调函数的定义了吧?

2. 为什么要使用回调函数?

很多朋友可能会想,为什么不像普通函数调用那样,在回调的地方直接写函数的名字呢?这样不也可以吗?为什么非得用回调函数呢?有这个想法很好,因为在网上看到解析回调函数的很多例子,其实完全可以用普通函数调用来实现的。要回答这个问题,我们先来了解一下回到函数的好处和作用,那就是解耦,对,就是这么简单的答案,就是因为这个特点,普通函数代替不了回调函数。所以,在我眼里,这才是回调函数最大的特点。来看看维基百科上面我觉得画得很好的一张图片。
在这里插入图片描述

#include<stdio.h>
#include<softwareLib.h> // 包含Library Function所在读得Software library库的头文件

int Callback() // Callback Function
{
    // TODO
    return 0;
}
int main() // Main program
{
    // TODO
    Library(Callback);
    // TODO
    return 0;
}

乍一看,回调似乎只是函数间的调用,和普通函数调用没啥区别,但仔细一看,可以发现两者之间的一个关键的不同:在回调中,主程序把回调函数像参数一样传入库函数。这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,这样有没有觉得很灵活?并且丝毫不需要修改库函数的实现,这就是解耦。再仔细看看,主函数和回调函数是在同一层的,而库函数在另外一层,想一想,如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数了,这也就是在日常工作中常见的情况。现在再把main()、Library()和Callback()函数套回前面 F1、F2和F3函数里面,是不是就更明白了?

在来看一个例子

#include<stdio.h>

int Function_1() // Callback Function 1
{
    printf("Hello, this is Callback_1 ");
    return 0;
}

int Function_2() // Callback Function 2
{
    printf("Hello, this is Callback_2 ");
    return 0;
}

int Function_3() // Callback Function 3
{
    printf("Hello, this is Callback_3 ");
    return 0;
}

int Handle(int (*Callback)())
{
    Callback();
}

int main()
{
    printf("Entering Main Function. ");
    Handle(Function_1);
    Handle(Function_2);
    Handle(Function_3);
    printf("Leaving Main Function. ");
    return 0;
}

在这个例子中,表面上看就是在Handle里面调用了三次Function,看上去也可以直接将Handle写成

int Handle(void)
{
    Function_1();
    Function_2();
    Function_3();
}

这样好像也能实现相似的功能,但是
1、如果Handle是下面这样,在调用回调前后还有其他操作,这时采用回调函数的方式才能实现代码间的低耦合

int Handle(int (*Callback)())
{
    printf("Entering Handle Function. ");
    Callback();
    printf("Leaving Handle Function. ");
}

2、如果Function函数是A团队写的,Handle函数是B团队写的,C团队复制写main函数的逻辑,此时C团队不能修改其他团队的代码,也就不能修改Handle函数,同样B团队不知道C团队调用Function 1 2 3的先后顺序(123,321,213等),也不能直接将Handle写死,这时采用直接调用函数的方式也就无法实现回调函数的效果
明白了回调函数的特点,是不是也可以大概知道它应该在什么情况下使用了?没错,你可以在很多地方使用回调函数来代替普通的函数调用,但是在我看来,如果需要降低耦合度的时候,更应该使用回调函数。
此外,不仅仅是团队间协作时回调函数能够发挥巨大作用,在不同项目间进行代码移植的时候,回调函数也能极大减少工作量

3. 怎么使用带参数的回调函数?

#include<stdio.h>

int Callback_1(int x) // Callback Function 1
{
    printf("Hello, this is Callback_1: x = %d ", x);
    return 0;
}

int Callback_2(int x) // Callback Function 2
{
    printf("Hello, this is Callback_2: x = %d ", x);
    return 0;
}

int Callback_3(int x) // Callback Function 3
{
    printf("Hello, this is Callback_3: x = %d ", x);
    return 0;
}

int Handle(int y, int (*Callback)(int))
{
    printf("Entering Handle Function. ");
    Callback(y);
    printf("Leaving Handle Function. ");
}

int main()
{
    int a = 2;
    int b = 4;
    int c = 6;
    printf("Entering Main Function. ");
    Handle(a, Callback_1);
    Handle(b, Callback_2);
    Handle(c, Callback_3);
    printf("Leaving Main Function. ");
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/357846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣-部门工资前三高的所有员工

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道稍微复杂的力扣sql练习题。 文章目录前言一、题目&#xff1a;185. 部门工资前三高的所有员工二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.其他总结前言 上一篇带大家练习了部门工资最高的…

CUDA硬件实现

CUDA硬件实现 文章目录CUDA硬件实现4.1 SIMT 架构4.2 硬件多线程NVIDIA GPU 架构围绕可扩展的多线程流式多处理器 (SM: Streaming Multiprocessors) 阵列构建。当主机 CPU 上的 CUDA 程序调用内核网格时&#xff0c;网格的块被枚举并分发到具有可用执行能力的多处理器。一个线程…

【C++】1.C++基础

1.命名空间 使用命名空间的目的是对标识符的名称进行本地化&#xff0c;以避免命名冲突或名字污染&#xff0c;namespace关键字的出现就是针对这种问题的。 1定义 定义命名空间&#xff0c;需要使用到namespace关键字&#xff0c;后面跟命名空间的名字&#xff0c;然后接一对…

DepGraph:适用任何结构的剪枝

文章目录摘要1、简介2、相关工作3、方法3.1、神经网络中的依赖关系3.2、依赖关系图3.3、使用依赖图剪枝4、实验4.1、设置。4.2、CIFAR的结果4.3、消融实验4.4、适用任何结构剪枝5、结论摘要 论文链接&#xff1a;https://arxiv.org/abs/2301.12900 源码&#xff1a;https://gi…

软考高级-信息系统管理师之质量管理(最新版)

质量管理目录 项目质量管理质量管理基础质量与项目质量质量管理质量管理标准体系1、IS09000系列,8项基本原则如下。2、全面质量管理(TQM)3、六西格码意为“六倍标准差”,4、软件过程改迸与能力成熟度模型项目质量管理过程规划质量管理1、规划质量管理2、规划质量管理:输入3、…

【java】Spring Cloud --Spring Cloud 的核心组件

文章目录前言一、Eureka&#xff08;注册中心&#xff09;二、Zuul&#xff08;服务网关&#xff09;三、 Ribbon&#xff08;负载均衡&#xff09;四、Hystrix&#xff08;熔断保护器&#xff09;五、 Feign&#xff08;REST转换器&#xff09;六、 Config&#xff08;分布式配…

【C++】RBTree——红黑树

文章目录一、红黑树的概念二、红黑树的性质三、红黑树节点的定义四、红黑树的插入五、代码实现一、红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过对任何一条从根到叶子的路径上…

Python爬虫(7)selenium3种弹窗定位后点击操作,解决点击登录被隐藏iframe无法点击的登陆问题

之前的文章有关于更多操作方式详细解答&#xff0c;本篇基于前面的知识点进行操作&#xff0c;如果不了解可以先看之前的文章 Python爬虫&#xff08;1&#xff09;一次性搞定Selenium(新版)8种find_element元素定位方式 Python爬虫&#xff08;2&#xff09;-Selenium控制浏览…

看见统计——第四章 统计推断:频率学派

看见统计——第四章 统计推断&#xff1a;频率学派 接下来三节的主题是中心极限定理的应用。在不了解随机变量序列 {Xi}\{X_i\}{Xi​} 的潜在分布的情况下&#xff0c;对于大样本量&#xff0c;中心极限定理给出了关于样本均值的声明。例如&#xff0c;如果 YYY 是一个 N(0&am…

Spring系列-2 Bean的生命周期

背景&#xff1a; 作为Spring系列的第二篇&#xff0c;本文结合容器的启动流程介绍单例Bean的生命周期&#xff0c;包括Bean对象的创建、属性设置、初始化、使用、销毁等阶段&#xff1b;在此过程中会介绍Spring用于操作Bean或者BeanDefinition的相关扩展接口。 文章重心在于介…

Spring Boot 日志文件,你都会了吗?

目录 1、日志文件的作用 2、日志的使用 2.1、从程序中得到日志对象 2.2、使用日志 2.3、日志格式 3、日志级别 3.1、这样的日志级别有什么用&#xff1f; 3.2、日志级别分类和使用 3.3、日志级别设置 4、日志持久化 5、更简单的日志输出——lombok 5.1、对比 5.2、…

阅读源码和查看官方文档,是解决问题最高效的办法。

作为一个工作8年的老程序员告诉你&#xff1a;阅读源码和查看官方文档&#xff0c;是解决问题最高效的办法。不信你来看&#xff0c;这个困扰了读者半天的问题我查了源码和文档后瞬间解决。 前言 上周五有位读者私信我一个问题&#xff0c;说困扰了他半天&#xff0c;研究了一…

利用Rust与Flutter开发一款小工具

1.起因 起因是年前看到了一篇Rust iOS & Android&#xff5c;未入门也能用来造轮子&#xff1f;的文章&#xff0c;作者使用Rust做了个实时查看埋点的工具。其中作者的一段话给了我启发&#xff1a; 无论是 LookinServer 、 Flipper 等 Debug 利器&#xff0c;还是 Flutt…

基于springboot+vue的疾病匿名检测查询系统

基于springbootvue的疾病匿名检测查询系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背…

【拥抱开源】发布自己的项目到maven中央仓库

文章目录&#x1f388;第一步&#xff0c;注册账号&#x1f4bf;第二步&#xff0c;登录&#x1f4c0;第三步&#xff0c;设置信息&#x1f4be;第四步&#xff0c;创建问题&#x1f4f9;第五步&#xff0c;验证信息&#x1f3a5;第六步&#xff0c;上传jar包到中央仓库&#x…

网页中字体混淆的处理(简易方法)——爬虫学习笔记

网页中字体混淆的处理——爬虫学习笔记之评论爬取一、网页分析1、打开某点评网址。2、在网页源代码中寻找评论信息&#xff08;1&#xff09;进入这个“火锅店”的详情页&#xff0c;然后右击“检查”&#xff08;2&#xff09;点上面“刷新”详情页&#xff0c;逐步寻找。&…

RK3288 GPIO记录

1、引脚对应的GPIO 编号第一种 使用/sys/kernel/debug/gpio查询所有gpio引脚的基数第二种 cat /sys/class/gpio/gpiochip248/label对应的label就是GPIO引脚&#xff0c;例如下图GPIO8对应的基数就是2482、计算编号编号 基数 PIN脚如GPIO8的基数是248&#xff0c; GPIO8_A6的编…

Java开发 - 数风流人物,还看“微服务”

目录 前言 服务器端的发展历程 早期的服务器 动态的页面 用户内容网站 微服务 企业级应用 互联网应用 微服务介绍 什么是微服务&#xff1f; 为什么使用微服务 怎么使用微服务 Spring Cloud 什么是Spring Cloud Nacos注册中心 什么是Nacos 创建微服务项目 创建…

【Servlet篇】一文带你吃透Request对象

文章目录1. 前言2. Request 对象2.1 Request 继承体系2.2 Request 获取请求参数1. 获取请求行数据2. 获取请求头数据3. 获取请求体数据4. 获取请求参数的通用方式3. IDEA中快速创建 Servlet你问我青春还剩几年&#xff1f;我的回答是&#xff0c;趁现在&#xff0c;正当时。身边…

CS144-Lab2

实验架构 除了写入传入流之外&#xff0c;TCPReceiver 还负责通知 sender 两件事&#xff1a; “First unassembled” 字节的索引&#xff0c;称为“acknowledgment”或 “ackno”。这是接收方需要来自发送方的第一个字节。“first unassembled ” 索引和“first unacceptable…