回调函数和注册回调函数你还不理解吗.......

简介: 回调函数和注册回调函数你还不理解吗.......

 关于回调函数这一块一直以来都是很多人在学习C语言、嵌入式软件这块比较难理解的东西,网上一搜“回调函数”、“回调函数的作用”、“回调函数的实现”等等,很多说的都是云里雾里的,基本都是说其实就是一个函数指针,把函数作为参数传递进去,网上的这些说法其实没毛病,也是事实,但是很多时候就很难理解这样做的目的是什么?网上再有就是说很灵活,但还是不能理解啊,就很抽象,于是今天专门来用微代码讨论下这个神奇的“回调”


     在说回调函数之前先来简单的复习下C语言中的函数指针,指针有很多类型,如char *、int *等等,分别是指向字符和整形变量的指针,那其实也很容易理解,也有一个指针指向一个函数,我们就把指向函数的指针,就叫做函数指针。这个地址保存的是函数的地址,我们知道这个函数的入口地址,自然也就能通过这个地址去运行这个函数。


       关于网上搜索相关回调函数的示例可能都是像下面这样的,把一个函数作为参数传递进去,然后运行,实现不同的功能,这种效果普通函数也能完成,为什么还要回调呢?这样做不是多此一举吗?


       上面的示例确实描述了回调函数的基本用法,但是完全没有示范清楚为什么需要回调函数,简单的来说,回调函数最大的作用的“解耦”,划重点。在嵌入式系统中芯片的功能越来越强大,能做的事情也越来越多,带来的就是代码量的快速膨胀,像Linux发展到现在已经2700万+的代码量了,代码量多的情况下为了更好的维护、更新、多人协作就需要软件分层设计,分层设计的最基本原则就是上层可以去调用下层的API接口,反之截止,即单向调用。一般嵌入式系统中会分成硬件接口层(BSP)、驱动层、中间件、操作系统、应用层、用户交互等等,不同复杂度的嵌入式系统的分层数都是不同的,下面我们就软件的分层思想来简单看下下面的示例代码,示例代码总共三个文件分别是main.c、dev.c、dev.h,把dev.c和dev.h划分为底层,main.c为上层应用,来实现一个错误输出的功能,即模拟dev是接收消息体,当接收到错误消息的时候根据不同的错误类型输出不同的错误信息。


不使用回调函数和函数指针也能实现,下面来看下代码实现:


   上面的代码也实现了这个功能,但是带来的问题是很明显的,这两个文件没有层次之分,dev.c调用了main.c中的接口,main.c调用了dec.c中的接口,极度耦合,当需要修改某一层的时候可能两个文件都需要修改,代码的耦合度太高,且没有遵循单向调用的规定。如果把main.c文件删除,dev.c是无法编译的。


   这时候有人可能会说,把error_func定义在dev.c中不就好了吗,完美解决耦合问题,确实,但是一般底层的文件都是以库或者其他形式存在,不允许上层修改底层甚至连底层的代码你都看不到,又何谈修改呢?再说如果谁都能去修改底层实现,多人开发一个项目的时候这不是很危险,所以一般底层都是提供固定的API接口给上层调用,但是仍有一些逻辑是需要分别处理,如举例的错误处理,可能底层出现不同的错误上层要做不同的操作,如释放内存、关闭进程、退出程序等等,这些是需要上层去实现的。


   有了上面的例子和问题点,再来看下怎么解决这类问题吧,解决的方式就是通过函数指针以及回调函数、注册回调函数的方式实现。

   

   相当清楚了吧,即便是我把main.c文件删除,dev.c都能正常编译,完全不依赖于mian.c的接口,他的逻辑很简单,不管dev.c运行的时候有没有错误先把出现错误需要调用的代码注册进去,发生错误的时候就能直接运行,如果不注册也没问题,也不影响dev.c的编译。


   那为什么会被称为回调函数(callback)呢,这也很好理解了,在main.c中定义,调用dev.c接口的时候运行到函数指针再回头调用main.c中的函数。运行完再回到dev.c中继续执行。


源码分享:

main.c

#include "dev.h"
/* 回调函数--发生错误的时候运行 */
void ErrorPrintf(void)
{
    printf("<%s> <%s>\t Device message error.\n", __FILE__, __func__);
    /* do something... */
    /* error handling code...*/
}
int main(void)
{
    /* 注册回调函数 */
    DeviceErrorRegister(ErrorPrintf);
    /* 调用底层开放接口 */
    GetDeviceMsg("false");
    return 0;
}

dev.h

#ifndef  __DEV_H__
#define  __DEV_H__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 错误处理函数指针 */
typedef void (*dev_err)(void);
extern void DeviceErrorRegister(dev_err err_func);
extern void GetDeviceMsg(char *msg);
#endif   /* __DEV_H__ */


dev.c

#include "dev.h"
dev_err error_func;
/* 注册回调函数接口 */
void DeviceErrorRegister(dev_err err_func)
{
    error_func = err_func;
}
void GetDeviceMsg(char *msg)
{
    printf("<%s>\t <%s>\t enter.\n", __FILE__, __func__);
    if(strstr(msg, "true"))
    {
        printf("<%s>\t <%s>\t get device msg and check pass.\n", __FILE__, __func__);
    }    
    else if(strstr(msg, "false"))
    {
        printf("<%s>\t <%s>\t receive false message.\n", __FILE__, __func__);
        if(error_func != NULL)
            error_func();
    }
    printf("<%s>\t <%s>\t exit.\n", __FILE__, __func__);
}

上述代码可以在VS Code中直接编译运行。


   到这里我相信大家对回调函数以及运行过程都有一个大概的了解了,如果觉得本篇文章多少有点帮助的话,求赞、关注、评论、转发,创作不易!你们的支持是小编创作最大动力。

相关文章
|
2月前
回调函数
【8月更文挑战第21天】
19 1
|
5月前
|
人工智能 机器人 中间件
【C++】C++回调函数基本用法(详细讲解)
【C++】C++回调函数基本用法(详细讲解)
|
5月前
|
消息中间件 存储 API
【C/C++】回调函数详解&注册窗口类&LRESULT & CALLBACK详解以及游戏中的应用
【C/C++】回调函数详解&注册窗口类&LRESULT & CALLBACK详解以及游戏中的应用
234 0
|
11月前
|
前端开发 JavaScript 测试技术
理解回调函数
理解回调函数
80 0
【回调函数】
【回调函数】
44 0
|
API C++
回顾C++回调函数
回顾C++回调函数
「C/C++」C/C++ 回调函数
「C/C++」C/C++ 回调函数
119 0
|
Java C语言 C++
c++ 回调函数的使用
java的回调函数可能都不陌生,使用接口interface的方式,在接口中定义回调函数。函数参数可以是interfance。调用函数的时候,实现这个interface的函数即可。
190 0
c++ 回调函数的使用