C语言学习及应用笔记之七:C语言中的回调函数及使用方式

小编 2026-06-12 阅读:1256 评论:0
我们在使用C语言实现相对复杂的软件开发时,经常会碰到使用回调函数的问题。但是回调函数的理解和使用却不是一件简单的事,在本篇我们根据我们个人的理解和应用经验对回调函数做简要的分析。 1、什么是回调函数 既...

我们在使用C语言实现相对复杂的软件开发时,经常会碰到使用回调函数的问题。但是回调函数的理解和使用却不是一件简单的事,在本篇我们根据我们个人的理解和应用经验对回调函数做简要的分析。

1、什么是回调函数

既然谈到了回调函数,首先我们就要搞清楚什么是回调函数。在讨论回调函数之前,我们需要说明另一个概念,那就是函数指针。什么是函数指针呢?说的浅显一点,函数指针就是指向函数的指针,说白了也是一种指针,只是它指向的不是整型,字符型等数据量,而是指向函数。在C中,每个函数在编译后都是存储在内存中,并且每个函数都有一个入口地址,根据这个地址,我们便可以访问并使用这个函数。函数指针就是指向这个入口地址,从而调用这个函数。

同样回调函数就是一个通过函数指针调用的函数。如果我们把函数的指针(指向函数入口地址)作为参数传递给另一个函数,而接收这个参数的函数在其运行过程中,反过来使用这个指针调用其所指向的函数,我们就把这个被通过函数指针调用的函数称之为回调函数。

从上述描述我们可以知道,回调函数有别于一般意义上的函数调用方式。它一般不是由该函数的实现方直接调用,而是由已经存在的其它对象间接调用它。而且回调函数的调用是调用方所需要的,但是其具体实现却是非常灵活的,我们可以根据需要来实现它,只要调用的格式相符,我们不需要去考虑调用他的对象的具体内容。

2、为何使用回调函数

前面我们简单介绍了回调函数,那我们为什么需要使用回调函数呢?既然是用它,当然是有使用的理由。接下来我们简单的讨论一下使用回调函数的优势所在。

首先,可以使上层的应用更完整,但又不需要考虑底层的实现细节。比如我们设计了一个通讯应用,但在设计时我并不能确定底层接口,或者说不想局限于某一接口。那么我们可以将接口部分的实现留在具体使用中,所以采用回调函数的方式就非常方便。

其次,可以使应用更加灵活,这是显而易见的。比如我们设计一个通讯协议栈,这个协议栈在什么平台使用并不局限,我们使用回调的方式具体实现平台相关部分,而协议栈的内核这可以使用于多种平台。

再者,可以把调用者与被调用者分开,这样调用者不关心谁是被调用者,也不关心他的具体实现。使得软件的设计更加独立,方便与协作或者移植。其实细说起来还有很多,在此仅列举上述几点。

3、如何使用回调函数

我们已经简单的介绍了什么事回调函数以及为什么要使用它,接下来我们说说怎么使用它。对于使用方式千差万别,而且每个使用者都有相应的心得,在这里我们之宗解一下我们平时常用的几种方式。

3.1、以函数参数的形式使用

在大多数情况下,我们可能都是将函数指针作为参数传递给调用者来实现回调。比如我们声明如下函数:

void function1(int var1,int var2)

void function2(void *fc(int,int),float a,int b)

调用时咋使用function2(function1,a,b)就可以了。当然还有另一个函数与function1的声明形式一致,也一样可以做为参数传递给function2函数。

这种方式最好理解,而且函数名不受限制,只要声明形式一致就可以了。我们在外设驱动的调用上会使用这一形式。

3.2、以弱化定义的方式使用

所谓弱化函数就是调用者以_weak定义一个没有操作或者默认操作的函数,该函数允许定义与其名称和形式完全一样的函数。若使用者重新定义了该函数则会调用新函数,否则使用_weak修饰的默认函数。在STM32的HAL库中使用了很多这样的函数,比如各种msp函数。

首先需要有一个以_weak修饰的函数声明:

__weak void SetSingleCoil(uint16_t coilAddress,bool coilValue)

而在使用时定义一个与其同名且形式一样的函数:

void SetSingleCoil(uint16_t coilAddress,bool coilValue),具体个功能有使用者更具需要设定。如上述这个函数就是我们在调用Modbus协议栈时实现的,每次都不一样,根据需求而定。

这种方式使用虽然方便,但有一个局限就是必须与原函数声明一致,且只能有一个。

3.3、以函数注册的方式使用

有时候我们会对一些对象进行封装,同是将操作函数的函数指针也封装在内,这样我们可以在使用对象是直接调用其操作。这以方式组要应用于对一些复杂的外设对象的操作。如:网卡对象等,在WIZnet以及LwIP等协议栈中都是以这种方式将网卡密切相关的特定操作以函数指针的方式封装于对象中。

当然我们在开发一些外设的驱动时也可以使用这种方式。如我们开发一个外设驱动,该设备即可使用I2C接口也可使用SPI接口,我们要多次使用该设备,但每次,每个人使用那种接口是不确定的,而我们又想复用这部分驱动,但不是每次都改它,就将其作为一个对象封装起来。

定义一个结构类型,包括包括对象的主要属性和基本操作接口:

/*定义BMP280操作对象*/

typedef struct {

  uint8_t chipID;       //芯片ID

  struct Bmp280_Calib_Param caliPara;   //校准参数

  struct Bmp280_Config config;  //配置寄存器

  struct Bmp280_Ctrl_Meas ctrlMeas;     //测量控制寄存器

  void (*Read)(uint8_t regAddress,uint8_t *rData,uint16_t rSize);       //读数据操作指针

  void (*Write)(uint8_t regAddress,uint8_t command);    //谢数据操作指针

  void (*Delay)(volatile uint32_t nTime);       //延时操作指针

}BMP280Device;

在使用时,我们只需声明某一特定对象,并注册相应的函数就可以使用,调用者并不关心具体接口实现。

3.4、以函数指针类型的方式使用

以声明函数指针类型的方式其实是与函数参数很类式的,也可用于形参声明,而且更简洁。但它最主要的优势在于我们可以使用其处理多个回调函数条件调用的问题。

据比如我们在处理Modbus协议时我们在处理不同功能吗的消息时,需要采用不同的处理方式,就可以采用这种方式:

定义一个枚举,同时定义一个函数指针数组:

void (*HandleSlaveRespond[])(uint8_t *,uint16_t,uint16_t)=

{HandleReadCoilStatusRespond,

                                                            HandleReadInputStatusRespond,

                                                            HandleReadHoldingRegisterRespond,

                                                            HandleReadInputRegisterRespond};

这要我们通过功能码的枚举来调用不同的回调函数就非常简洁了:

HandleSlaveRespond[fuctionCode](recievedMessage,startAddress,quantity);

当然,我们只是讨论一种方法,因为使用switch语句一样可以达到效果,但是其代码量却是相差很远。

4、总结

此篇我们介绍了回调函数及其使用方式,但我们所掌握的不过冰山之一角。并且具体怎么使用它是一个见仁见智的论题,用好了自然是给程序增色,但若是随意使用反倒游客能会有问题。总而言之,回调函数是一种灵活而有强大的功能,但最终的效果还要看使用者。

欢迎关注:

\"\"

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表