C语言 —— 回调函数

回调函数是一种将函数作为参数传递给另一个函数的技术,用于实现延迟执行和解耦。在C语言中,回调通过函数指针实现,允许在库函数执行完成后调用用户自定义的处理函数。这种机制常用于异步处理,避免阻塞主线程,并且能够根据需要选择不同的处理方式,实现多态。回调函数在事件驱动编程中尤为关键,例如在处理耗时操作时,主程序可以继续执行,待操作完成后再调用回调进行后续处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是回调函数

回调并不是“你我”两方的互动,而是ABC的三方联动。A调用B,B在处理完之后调用C。
在这里插入图片描述主要是不同模块之间的调用,比如有模块A和B,A可以将自己要调用的函数传给B,然后A在调用B时,B完成后就执行A传来的函数,这个函数 可以去调用模块C。
当然也可以调用模块A。
所以该功能主要是为了能够将实现不同模块的通信:
通常我们上层调用下层API,而下层不调用上层。而如果我们下层想和上层通信,通过回调,我们只需要在A调用B时将回调函数传给B,B在执行完任务后就可以调用A指定的函数,从而实现双向通信

举个例子来说明回调函数:

打个比方,有一家旅馆提供叫醒服务,旅客可以自己决定叫醒方法,比如让客房打电话,或者敲门。

  • 首先在主程序中调用库函数,主函数就是旅客库函数就是旅店提供的叫醒服务
  • 然后把回调函数指针传入库函数中
  • 回调函数是我们自己写,就是我决定的叫醒方法后的工作
  • 比如旅客第一天告诉旅店使用打电话叫醒服务,并且自定义接到电话的回调函数就是接到电话后再继续睡一会儿。那么宾馆会在第二天进行回调旅客定义打电话的方法,旅客选择在回调函数中进行处理接到电话后要继续睡觉
  • 旅客第三天告诉旅店使用敲门方式叫醒并且敲门的自定义回调函数为立即起床。那么宾馆会在第二天调用敲门的回调函数,旅客在敲门回调函数被调用后立即起床。

上面的例子说明了回调的两个作用,一个是延迟回调,也就是我告诉宾馆第二天叫我,我就可以干我自己的事儿了。另一个优势是选择方式,就是我可以在回调函数中定义不同的处理方式,敲门,和打电话的处理方式就不同。

回调函数作用

https://www.runoob.com/w3cnote/c-callback-function.html

1. 实现多态(解耦)

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

#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函数里面,是不是就更明白了?

明白了回调函数的特点,是不是也可以大概知道它应该在什么情况下使用了?没错,你可以在很多地方使用回调函数来代替普通的函数调用,但是在我看来,如果需要降低耦合度的时候,更应该使用回调函数。

2. 延迟异步调用

这个应该是回调函数的最大重要的作用
项目中使用的都是都是异步,我觉得最大的用处就是异步,延迟回调。
当一个操作非常耗时,而且可能出现崩溃的情况下,如果我们还使用同步回调,那么我们就要一直等这个操作执行完成才会回调。而这时我们可以使用异步回调函数。主函数调用这个耗时函数之后会继续往下走,而不会被阻塞住。这个耗时操作就自己去执行,等执行完成之后会调用回调函数,这样不影响主函数的其他操作。
异步的功能是底层提供给上层的, 我们自己写的回调函数,但是比如上面的例子中,是同步回调。
下面两幅图展示了同步回调与异步回调的区别:

同步回调

在这里插入图片描述

异步回调

在这里插入图片描述

回调函数的使用

函数指针

https://www.cnblogs.com/zhengjiafa/p/5796283.html
作用:因为c语言中只有struct而struct不像c++中的class,他是没有成员函数的,所以使用函数指针,可以将函数作为成员函数
函数指针有两个用途:调用函数、做函数的参数(主要用途)
函数指针实例:

#include <stdio.h>

int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
//函数定义
int Func(int x) {
    printf("asdf");
    return 1;
}

char (*PTRFUN1)(int);//定义一个函数指针
char glFun(int a)
{ 
    printf("out ");
    return 'd';
}
int main()
{
    p = Func;          /*将Func函数的首地址赋给指针变量p*/
    (*p)(2); //调用函数方式一
    PTRFUN1 = glFun; //将glFun函数首地址给指针变量
    PTRFUN1(2); //调用函数方式二

}

该实例并没有体现出函数指针的好处,反而使用起来更复杂。
但是通过下面的回调函数,我们就能看到函数指针的真正威力了,将函数作为参数传入另一个函数

回调函数示例代码

异步回调最本质上就是事件驱动编程。
特性:使用函数的指针最重要的作用就是作为函数的参数。这在回调函数中完美体现。
正常情况下我们无法将一个函数传入另一个函数中。但是使用函数指针,我们可以将一个函数传入另一个函数中,这个特性在回调函数中使用,体现的淋漓尽致**。


//文件B.c
//形参使用函数指针,传入的是函数的首地址,这样我们就可以实现将函数传入另一个函数中了
//这里形参是一个函数指针,他可以接收其他函数传入。
//回调类型和库函数名称类似
void Handle(int y, int (*HandleCallbackT)(int)) 
{
    printf("Entering Handle Function. ");
    printf("\r\n");
    HandleCallbackT(y); //调用回调函数
    printf("Leaving Handle Function. ");
    printf("\r\n");
}

//文件A.c
int Callback_1(int x) // Callback Function 1
{
    printf("Hello, this is Callback_1: x = %d ", x);
    printf("\r\n");
    return 0;
}

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

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

//文件A.c
//callBack调用函数,调用函数和回调函数名称类似,因为他们是同一个文件,同一级
void CallBack() {
    int a = 2;
    int b = 4;
    int c = 6;

    //调用库函数,将回调函数传入,会在库函数中走回调函数
    //在哪里调用这个库函数,就在哪里写回调函数定义。
    Handle(a, Callback_1);//将callback1传入
    Handle(b, Callback_2);
    Handle(c, Callback_3);
}

//test测试
int main()
{
    printf("Entering Main Function. ");
    printf("\r\n");
    CallBack();
    //这里体现出优势,为何使用回调,其实就是为了解耦,我们可以将不同的回调函数传入handle
    //并且不需要改变handle函数,而我们可以传入不同的函数参数
    printf("Leaving Main Function. ");
    printf("\r\n");
    return 0;
}

最重要的是要想清楚,在Handle函数调用之后,handle函数会调用回调函数,我们这时应该做什么,应该如何处理。

### C语言实现串口回调函数解析接收数据方法 在嵌入式开发中,利用回调函数可以有效地处理串口接收到的数据。这种方式不仅提高了代码的灵活性和可维护性,还使得程序结构更加清晰。 #### 定义回调函数原型 首先定义一个用于处理接收到的数据帧的回调函数原型。该函数接受两个参数:一个是缓冲区地址;另一个是接收到的有效字节数量[^3]。 ```c // 声明回调函数类型 typedef void (*FrameHandler)(uint8_t *buffer, size_t length); ``` #### 初始化串口并注册回调 接着,在初始化串口通信时指定上述类型的函数作为事件处理器。每当有新消息到达时就会自动触发此函数执行相应操作[^1]。 ```c void setup_uart(FrameHandler handler) { // 配置UART硬件... // 设置中断服务例程或其他机制以调用handler当有可用字符时 } ``` #### 编写具体业务逻辑 最后一步就是根据实际应用场景编写具体的业务逻辑了。这通常意味着要分析每一帧的内容,并据此采取适当的动作——可能是更新状态机、记录日志或是向其他设备转发信息等等[^4]。 ```c void process_frame(uint8_t *data, size_t len) { static uint8_t frame_buffer[FRAME_MAX_SIZE]; static size_t buffer_pos = 0; for (size_t i = 0; i < len; ++i) { if (is_start_of_frame(data[i])) { // 判断是否为起始标志 buffer_pos = 0; } else if (is_end_of_frame(data[i]) && buffer_pos > 0) { // 结束标志且非空 memcpy(frame_buffer + buffer_pos++, &data[i], sizeof(data[i])); // 处理完整的一帧数据 handle_complete_frame(frame_buffer, buffer_pos); buffer_pos = 0; // 清除位置准备下一个周期 } else if(buffer_pos < FRAME_MAX_SIZE){ memcpy(frame_buffer + buffer_pos++, &data[i], sizeof(data[i])); } } } setup_uart(process_frame); // 注册process_frame作为回调函数 ``` 通过以上步骤可以在C语言环境中成功地实现了基于回调机制的串口数据解析方案。这种方法能够显著简化复杂系统的构建过程,同时也提供了更好的性能表现。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Charles Ray

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值