C语言的回调函数,C 回调函数
当我们使用C语言实现相对复杂的软件开发时,经常会遇到使用回调函数的问题。但是,回调函数的理解和使用并不是一件简单的事情。本文根据个人理解和应用经验,对回调函数做一个简要的分析。
1.什么是回调函数?
既然说到回调函数,首先我们要搞清楚什么是回调函数。在讨论回调函数之前,我们需要解释另一个概念,即函数指针。什么是函数指针?说白了,函数指针就是指向函数的指针。说白了也是指针,只是指向的是函数而不是整数或者字符。在C中,每个函数编译后都存储在内存中,每个函数都有一个入口地址,根据这个地址我们可以访问和使用这个函数。函数指针指向这个入口地址来调用这个函数。
类似地,回调函数是由函数指针调用的函数。如果我们将一个函数的指针(指向一个函数的入口地址)作为参数传递给另一个函数,而在其运行过程中接收到这个参数的函数又用这个指针调用被指向的函数,我们就把这个被函数指针调用的函数称为回调函数。
从上面的描述中,我们可以知道回调函数不同于一般的函数调用方法。一般不是由这个函数的实现者直接调用,而是由其他已有的对象间接调用。而且回调函数是调用者调用的,但是具体实现非常灵活。我们可以根据需要来实施。只要调用格式一致,我们就不需要考虑对象调用他的具体内容。
2.为什么要用回调函数?
我们简单介绍了回调函数,那么为什么需要使用回调函数呢?既然用了,当然有用的理由。接下来,我们简单讨论一下使用回调函数的好处。
首先,它可以使上层的应用更加完整,但不需要考虑下层的实现细节。比如我们设计了一个通讯应用,但是我在设计的时候并不确定底层的接口,或者说我不想局限在某个接口上。然后我们可以把接口部分的实现留给具体使用,这样采用回调函数就很方便了。
其次,很明显,应用程序可以变得更加灵活。比如我们设计一个通信协议栈,这个协议栈可以用在什么平台上是没有限制的。我们使用回调来实现平台的相关部分,协议栈的内核可以在各种平台上使用。
再者,可以把调用者和被调用者分开,让调用者不在乎被调用者是谁,也不在乎他的具体实现。使软件设计更加独立、方便、协作或可移植。其实细节很多,这里就说几点。
3.如何使用回调函数
我们已经简要介绍了什么是回调函数以及为什么要使用它。接下来,我们来谈谈如何使用它。有很多不同的使用方法,每个用户都有自己的体验。在这里,我们来说一些常用的方式。
3.1.以函数参数的形式使用。
大多数情况下,我们可能会将函数指针作为参数传递给调用者来实现回调。例如,我们声明以下函数:
void函数1(整型变量1,整型变量2)
void function2(void *fc(int,int),float a,int b)
打电话的时候用function2(function1,a,b)就行了。当然,还有一个函数与function1的声明形式一致,也可以作为参数传递给function2。
这个方法最好理解,函数名不限,只要声明形式一致即可。我们将在外围设备驱动的呼叫中使用这种形式。
3.2.以削弱定义的方式使用。
所谓弱化函数,就是调用者用_weak定义一个没有操作或者缺省操作的函数,允许定义一个具有确切名称和形式的函数。如果用户重新定义函数,将调用新的函数;否则,将使用用_weak修饰的默认函数。STM32的HAL库中使用了很多这样的函数,比如各种msp函数。
首先,您需要一个用_weak修饰的函数声明:
_ _弱空set single coil(uint 16 _ t coil address,bool coilValue)
并在使用时定义一个同名同形的函数:
void setsinglecool(uint 16 _ tcoil地址,bool线圈值),具体功能需要用户多设置。比如上面的函数就是我们调用Modbus协议栈的时候实现的,每次都不一样,具体看需求。
这种方法虽然使用方便,但是有一个限制,就是必须和原函数声明一致,而且只能有一个。
3.3.以函数注册的形式使用。
有时候我们会封装一些对象,包括操作函数的函数指针,这样在使用的时候就可以直接调用对象的操作。这样,组应该应用于一些复杂的外围对象的操作。比如网卡对象等。在WIZnet和LwIP协议栈中,与网卡密切相关的具体操作就是这样以函数指针的形式封装在对象中的。
当然,我们在开发一些外设驱动时也可以使用这种方法。如果我们开发一个外设驱动程序,该设备可以使用I2C接口或SPI接口。我们要多次使用设备,但每次都不确定大家会用哪个接口,我们想重用这部分驱动,但不是每次都换,而是把它打包成一个对象。
定义一个结构类型,包括对象的主要属性和基本操作接口:
1 /*定义BMP280操作数*/
2
3 typedef结构{
四
5 uint8 _ t chipID//芯片ID
六
7 struct BMP 280 _ Calib _ Param caliPara;//校准参数
八
9 struct Bmp280 _ Config config//配置寄存器
10
11 struct BMP 280 _ Ctrl _ Meas Ctrl Meas;//测量控制寄存器
12
13 void(* Read)(uint 8 _ t regAddress,uint8_t *rData,uint 16 _ t rSize);//读取数据操作指针
14
15 void(* Write)(uint8_t regAddress,uint 8 _ t命令);//感谢数据操作指针。
16
17 void(* Delay)(volatile uint 32 _ t nTime);//延迟操作指针
18
19 } BMP 280设备;使用时,我们只需要声明一个特定的对象,注册相应的函数就可以使用了。调用者不关心具体的接口实现。
3.4.将其用作函数指针类型。
其实声明函数指针类型的方式和函数参数类似,也可以用于形参声明,而且更简洁。但它的主要优点是我们可以用它来处理多个回调函数的条件调用问题。
比如我们在处理Modbus协议时,在处理不同功能的报文时,需要采用不同的处理方法,那么我们可以采用这种方法:
同时定义枚举和函数指针数组:
1 void(* handleslaveresponse[])(uint 8 _ t *,uint16_t,uint 16 _ t)=1
2
3 {HandleReadCoilStatusRespond,
四
5 HandleReadInputStatusRespond,
六
7 handlereadholdingregisterspond,
八
9 handlereadinputregisteresport };我们通过枚举函数代码来调用不同的回调函数非常简单:
handleslaveresponse[functioncode](已接收消息,起始地址,数量);
当然我们只讨论一种方法,因为switch语句也能达到同样的效果,但其代码量相差甚远。
4.摘要
在本文中,我们介绍了回调函数及其用法,但是我们所知道的只是冰山一角。而具体怎么用,则是众说纷纭的话题。如果使用得当,自然会为节目增色,但如果随意使用,游客就有问题了。总而言之,回调函数是一个灵活而强大的函数,但最终的效果取决于用户。
欢迎关注:
想更方便及时的阅读相关文章,请关注我的微信微信官方账号【木南创智】
转载请联系作者取得转载授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。