打印
[疑难问答]

c语言回调函数的使用及实际作用详解

[复制链接]
3563|39
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
macpherson|  楼主 | 2024-11-30 22:30 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

在51的时候基本一个.c文件解决,包括寄存器配置啊,产品功能啊。

这种就是没有架构的程序,然后我们进化到STM32这个单片机以后,程序大了,慢慢也会在工程文件里加几个文件夹目录把硬件层和应用层代码分开了。

于是我们会把一些不同的外设功能,比如Led、按键、串口等外设功能代码分别写在不同的.c文件里,然后统一用函数接口去调用它。

比方说控制一个LED灯亮,直接在led.c文件里写一个驱动led灯状态的函数然后给外部调用就好了。

那我们我们看这种Led的控制函数确实也是满足程序架构的需求的,硬件层和应用层代码分开,应用层用硬件层提供的接口来控制,而且又不会有硬件层和应用层共享的全部变量或数组。像这种是不是很简单?

那么不知道你们有没有碰到另外一种情况,就是应用程序需要采集硬件层的数据,比如串口接收数据,按键采集、ADC值采集。

这种硬件层的数据怎么通知应用层来拿,或者怎么主动给它?

我们以往最简单粗暴的方式是不是就是用一个全局变量,比方说硬件层串口接收到数据来了,那么我们把数据丢到数组里,然后把接收完成全局变量标志位置1。

比方说全局变量名为RcvFlag,然后应用层程序会轮询判断RcvFlag==1?是的话就开始把数组里的数据取出来解析。

很多人就会说了,你看我用这种方法照样能实现功能啊,为什么还要学习别的架构。

这样做当然可以实现功能,但是会存在移植性很差的问题。

比如说你们老板让你把这个串口的硬件层封装起来给客户用,但不能让客户看到你实现的源代码,只提供接口(函数名)给对方用。

那么这时候难道你要告诉客户先判断哪个变量为1,然后再取哪个数组的数据这么LOW的做法吗?

那么如果是懂行的客户一定会怀疑你们公司的技术实力是不是小学生水平。

那怎样做才会既方便又专业呢? 这里我们就需要用到回调函数啦。


三、回调函数的作用

那么在讲回调函数之前呢,对于函数调用呢我一般分为2种类型:

1.输出型

不知道大家有没有用过C语言自带的一些库函数,比如说sizeof()获取数据长度的函数,memcpy()是内存拷贝函数,我们调用这个函数之后呢就能完成相应的功能。

还有我们基于单片机的一些程序函数,比方说控制LED点亮熄灭、继电器吸合断开、LCD驱动等等。

那么这些呢,我一般称为输出型的函数。

输出型函数我们是主导的角色,我们知道什么时候该调用它。

2.输入型

输入型呢,也称为的是响应式的函数。

什么叫响应式的函数呢?

比方说接收串口的数据,我们不知道什么数据什么时候来。

再比方说,我们按键检测的函数,我们不知道什么时候会按下按键,那么这些就要定义成响应式函数来实现,而响应式函数就可以用回调函数来实现。

所以通过这两个种类型的分析啊,我们就可以知道,回调函数基本是用在输入型的处理中。

比方说串口数据接收,那么数据是输入到单片机里面的,单片机是处于从机角色。

按键检测,按键状态是输入到单片机里的。

再比方说ADC值采集,ADC值也是输入到单片机里的。

那么它们输入的时间节点都是未知的,这些就能够用回调函数来处理。

具体怎么处理后面我们会用代码来给大家举例。

回调函数还有一个作用就是为了封装代码。

比如说做芯片或者模组的厂家,我们拿典型的STM32来举例,像外部中断、定时器、串口等中断函数都是属于回调函数,这种函数的目的是把采集到的数据传递给用户,或者说应用层。

所以回调函数的核心作用是:

1.把数据从一个.c文件传递到另一个.c文件,而不用全局变量共享数据这么LOW的方法。

2.对于这种数据传递方式,回调函数更利于代码的封装。

四、掌握回调函数的程序编写

前面说了很多概念性的东西,可能大家也比较难理解,回调函数最终呢是靠函数指针来实现的。

那么我这里通过一些模拟按键的例子来演示下怎么回通过调函数来处理它们。

下面是我们的c-free工程,用这个来模拟方便点:

从模块化编程的思想来看,整个工程分为2个部分,应用层main.c文件,硬件层key.c和key.h文件。

不管再怎么复杂的程序,我们都要先从main函数一步步往下挖,main函数代码如下。

int main(int argc, char *argv[]){KeyInit();KeyScanCBSRegister(KeyScanHandle);KeyPoll();return 0;}

KeyInit();是key.c文件的按键初始化函数

KeyScanCBSRegister(KeyScanHandle);是key.c的函数指针注册函数。

这个函数可能大家会有点蒙,请跟进我们的节奏,下面开始烧脑环节,也是写回调函数的必须步骤,

想理解这个回调函数注册函数,我们要先从硬件层(key.h)头文件的函数指针定义说起,具体看下图。

这里自定义了一个函数指针类型,带两个形参。

然后,我们在key.c这个文件里定义了一个函数指针变量。

重点来了,我们就是通过这个函数指针,指向应用层的函数地址(函数名)。

具体怎么实现指向呢?就是通过函数指针注册函数。

这个函数是在main函数里调用,使用这种注册函数的方式注册灵活性也很高,你想要在哪个.c文件使用按键功能就在哪里调用。

这里要注意,main.c这个文件要定义一个函数来接收硬件层(key.c)过来的数据。

这里定义也不是乱定义的,一定要和那个自定义函数指针类型返回值、形参一致。

然后把这个函数名字直接复制给KeyScanCBSRegister函数的形参就可以了。

这样调用后,我们key.c文件的pKeyScanCBS这个指针其实就是指向的KeyScanHandle函数。

也就是说执行pKeyScanCBS的时候,就是执行KeyScanHandle函数。

那具体检测按键的功能就是KeyPoll函数,这个在main函数里调用。

当检测到键盘有输入以后,最终会调用pKeyScanCBS。

最终执行的是main.c文件的KeyScanHandle函数。

所以,我们来看下输出结果。

如果还是有点模糊,下面我再给大家捋一捋编写和使用回调函数的流程:

  • 自定义函数指针,形参作为硬件层要传到应用层的数据。
  • 硬件层定义一个函数指针和函数指针注册函数。
  • 应用层定义一个函数,返回值和形参都要和函数指针一致。
  • 应用层调用函数指针注册函数,把定义好的函数名称作为形参传入。

使用特权

评论回复
沙发
benjaminka| | 2024-12-2 19:31 | 只看该作者
C语言中一种常见的编程模式,其核心思想是将一个函数作为参数传递给另一个函数,在特定条件下由被调用函数来执行这个回调函数。

使用特权

评论回复
板凳
eefas| | 2024-12-3 07:58 | 只看该作者
在异步编程中,回调函数常用于处理异步操作的结果。例如,在网络编程中,当数据到达时,可以通过回调函数来处理接收到的数据。

使用特权

评论回复
地板
alvpeg| | 2024-12-6 21:51 | 只看该作者
通过回调函数,可以将通用的代码和特定的行为分离,提高代码的复用性和模块化。

使用特权

评论回复
5
kmzuaz| | 2024-12-6 23:08 | 只看该作者
许多库函数允许用户通过回调函数来扩展其功能,例如,数据库库可能允许用户定义自己的错误处理函数

使用特权

评论回复
6
macpherson|  楼主 | 2024-12-7 07:34 | 只看该作者
回调函数可以将调用者和被调用者解耦,使得调用者不需要知道被调用者的具体实现细节,只需要知道被调用者提供了一个特定的接口。

使用特权

评论回复
7
10299823| | 2024-12-9 11:57 | 只看该作者
通过合理使用回调函数,可以提高代码的可维护性和可扩展性,使得程序更加灵活和高效。

使用特权

评论回复
8
modesty3jonah| | 2024-12-9 12:09 | 只看该作者
#include <stdio.h>

// 声明函数指针类型
typedef int (*compare_func)(int, int);

// 定义回调函数
int ascending_compare(int a, int b) {
    return a - b;
}

int descending_compare(int a, int b) {
    return b - a;
}

// 排序函数,使用回调函数来比较元素
void sort(int arr[], int len, compare_func compare) {
    for (int i = 0; i < len; i++) {
        for (int j = i + 1; j < len; j++) {
            if (compare(arr[i], arr[j]) > 0) {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {3, 1, 4, 1, 5};
    int len = sizeof(arr) / sizeof(arr[0]);

    // 使用升序比较函数
    sort(arr, len, ascending_compare);
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 使用降序比较函数
    sort(arr, len, descending_compare);
    for (int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

使用特权

评论回复
9
updownq| | 2024-12-9 16:04 | 只看该作者
在C语言中,回调函数是一种常用的编程技巧,它允许用户将函数作为参数传递给另一个函数,这样在被调用函数执行时可以“回调”传入的函数。

使用特权

评论回复
10
yorkbarney| | 2024-12-10 17:25 | 只看该作者
回调函数是C语言中一种强大的编程技术,它允许我们在运行时动态地指定要调用的函数。

使用特权

评论回复
11
hilahope| | 2024-12-10 21:22 | 只看该作者
回调函数是一种通过函数指针实现的编程技术。它允许一个函数在运行时将另一个函数作为参数传递给它,并在适当的时候调用这个传递进来的函数。

使用特权

评论回复
12
vivilyly| | 2024-12-11 15:59 | 只看该作者
如果回调函数涉及到动态内存分配,需要注意内存的管理,避免内存泄漏。

使用特权

评论回复
13
51xlf| | 2024-12-11 18:32 | 只看该作者
编写一个接受函数指针作为参数的函数,并在适当的时候调用这个回调函数。

使用特权

评论回复
14
jonas222| | 2024-12-12 11:11 | 只看该作者
回调函数(Callback Function)是C语言中一种非常有用的编程技术,它允许将一个函数作为参数传递给另一个函数,从而实现更灵活和动态的代码执行。回调函数在许多场景下都非常有用,比如事件驱动编程、异步操作、以及实现某些设计模式等。

使用特权

评论回复
15
jimmhu| | 2024-12-12 17:42 | 只看该作者
通过传递不同的回调函数,我们可以改变程序的行为而无需修改主逻辑。这在实现插件系统或可扩展的应用程序时特别有用。

使用特权

评论回复
16
macpherson|  楼主 | 2024-12-12 22:05 | 只看该作者
回调函数是一种通过函数指针调用的函数。通常,回调函数被定义在某个地方,然后将其地址传递给另一个函数,以便在适当的时候调用这个回调函数。

使用特权

评论回复
17
adolphcocker| | 2024-12-13 20:57 | 只看该作者
这种机制在事件驱动编程、库函数的设计以及需要执行特定操作而无需知道具体实现细节的场景中非常有用。

使用特权

评论回复
18
kkzz| | 2024-12-13 22:48 | 只看该作者
当这些事件发生时,系统调用相应的回调函数来处理这些事件。

使用特权

评论回复
19
claretttt| | 2024-12-14 12:44 | 只看该作者
回调函数是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。简单来说,回调函数就是被作为参数传递给另一个函数的函数。

使用特权

评论回复
20
tabmone| | 2024-12-14 15:19 | 只看该作者
通过回调函数,可以在运行时动态地改变函数的行为,而不需要修改调用者的代码。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

46

主题

1657

帖子

1

粉丝