【C语言】函数指针的使用分析:回调、代码逻辑优化、代码架构分层

1、回调函数

1.1、回调函数的优缺点

  • 核心思想是反向调用:提前注册好调用函数,当指定事件发生时,请自动调用我预先注册好的这个函数
  • 优点:
    • 解耦与模块化:
      • 调用方(框架、库)和实现方(业务代码)不需要知道对方的具体实现细节。它们只通过一个预定义的函数接口(回调函数的原型)进行通信。
      • 比如:写业务代码时有业务需要定时被执行,可以调用库函数里的定时器注册函数,传入回调函数和定时周期,这样就可以实现函数被周期调用,而调用方不用关系定时器的内部实现细节
      • 便于团队协作开发:在嵌入式开发中,特别是使用linux系统的产品开发中,软件大致会分为两拨人:linux系统开发(内核态)和业务开发(用户态)。系统开发主要维护linux系统基本功能,开发底层框架;业务开发则使用框架来开发应用层业务。在项目初期,双方约定好回调接口形式,之后就可以并行开发,双方都不必关心对方的实现细节。
    • 可以实现异步调用和事件驱动框架
      • 当程序发起一个比较耗时的操作(比如DMA搬运),当操作完成后程序需要做某些处理。有两种处理方式:阻塞等待、回调函数。
      • 阻塞等待:程序不停查询DMA搬运是否完成,在此期间不能及时响应其他事件
      • 回调函数:注册好回调函数,当DMA搬运完成时调用回调函数通知DMA搬运完成,可进行相应处理
      • 好处:允许程序在发起一个耗时的操作(如IO请求、网络下载、DMA搬运)后不阻塞等待,而是继续执行后续代码。当那个操作完成后,再通过回调函数来通知和处理结果。
  • 缺点:
    • 过度嵌套回调会导致代码难以阅读和维护
    • 在代码中可以看到回调函数被注册,

1.2、以中断回调为例

c 复制代码
/*
	irq:中断号
	handler:中断回调处理函数
	trigger:触发方式
	name:中断名字
*/
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long trigger, const char *name);
  • 假设DMA完成使用的中断号是1,处理DMA搬运的流程:
    • 注册DMA搬运完成的中断处理函数,注册时选择中断号1
    • 发起DMA搬运
    • 处理其他业务
    • 当DMA搬运完成时上报中断,注册的回调函数被调用,进行相应处理

2、代码逻辑优化

2.1、不使用函数指针

c 复制代码
#include <stdio.h>
#include <string.h>

typedef void (*CommandHandler)();

void help() { printf("Showing help...\n"); }
void quit() { printf("Exiting...\n"); }
void run() { printf("Running program...\n"); }

int main() {
    char userInput[20];
    printf("Enter a command (help, quit, run): ");
    scanf("%s", userInput);

	if(strcmp("help", userInput) == 0)
	{
		help();
	}
	else if(strcmp("quit", userInput) == 0)
	{
		 quit();
	}
	else if(strcmp("run", userInput)
	{
		run();
	}
	else
	{
		 printf("Unknown command.\n");
	}
	
    return 0;
}

2.2、使用函数指针

c 复制代码
#include <stdio.h>
#include <string.h>

typedef void (*CommandHandler)();

void help() { printf("Showing help...\n"); }
void quit() { printf("Exiting...\n"); }
void run() { printf("Running program...\n"); }

struct Command {
    char name[20];
    CommandHandler handler;
};

// 函数指针查找表
struct Command commands[] = {
    {"help", help},
    {"quit", quit},
    {"run", run}
};

int main() {
    char userInput[20];
    printf("Enter a command (help, quit, run): ");
    scanf("%s", userInput);

    int numCommands = sizeof(commands) / sizeof(commands[0]);
    for (int i = 0; i < numCommands; i++) {
        // 在表中查找匹配的命令
        if (strcmp(commands[i].name, userInput) == 0) {
            commands[i].handler(); // 通过函数指针调用对应的处理函数
            return 0;
        }
    }
    printf("Unknown command.\n");
    return 0;
}

2.3、两种实现对比

  • 不使用函数指针:
    • 代码结构会显示冗余,支持的命令越多,if判断分支就越复杂,不利于看代码和代码维护
  • 使用函数指针:
    • 不管支持多少个命令都是一个for循环进行判断
    • 代码结构简单, 便于阅读、维护

3、代码架构分层

3.1、分层思想和抽象层隔离

  • 代码架构分层:
    • 就是将代码按照一定层次结构进行组织,每层都会对外提供交互的接口,各层之间通信只用关心暴露的对外接口,而不必关心层次内部的实现细节。
    • 每层都管理向下一层,并向上一层提供服务,并且不能越级访问。比如应用软件要操作硬件必须通过操作系统,不能越过操作系统去操作硬件
    • 每一层都有相对完整的功能,代码层级清晰,可以支持并行开发,没个层次的软件开发人员按照对外接口进行开发程序,然后再联调
    • 层次又可以细分出一些抽象层,抽象层就是把同一类事物的共同点抽象出来,并不具体指向某个事物,但是抽象出来的特征是这一类事物都共有的。
    • 抽象层的作用是可以起到隔离的作用:比如我们可以把操作系统看做是一个抽象层,底层硬件不管怎么变(arm架构、x86结构、riscv架构),操作系统对上层软件提供的接口是保持不变的,应用软件不必感知底层硬件的变动

3.2、led驱动子系统

c 复制代码
/* 截取自linux*/
struct led_classdev {
   
    const char *name; // LED 的名称,在 /sys/class/leds/ 下创建的目录名
    unsigned int brightness; // 当前的亮度值
    unsigned int max_brightness; // 最大亮度值
    int flags; // 控制LED行为的标志位

    // 设置亮度的主函数(允许睡眠/阻塞)
    int (*brightness_set_blocking)(struct led_classdev *led_cdev,
                    enum led_brightness brightness);
    // 获取亮度的函数(可选)
    enum led_brightness (*brightness_get)(struct led_classdev *led_cdev);
   ...........
 }
  • 在linux的led驱动子系统中,使用led_classdev结构体来描述led,里面描述了led的共性特征,其中使用函数指针来表示led的操作函数
  • 不管是什么硬件平台的led灯,都会有设置亮度、获取亮度的操作。在硬件初始化时,不同的硬件平台对函数指针赋值成不同的操作函数
  • 上层应用操作led时只需要调用这两个函数指针,而具体的操作细节不必关系,这样不管底层硬件如何变,只用系统开发人员适配好,上层业务代码是不需要变动的
相关推荐
饭碗的彼岸one3 小时前
C++ 并发编程:异步任务
c语言·开发语言·c++·后端·c·异步
云和数据.ChenGuang4 小时前
微服务技术栈
微服务·云原生·架构
水饺编程4 小时前
Windows 命令行:cd 命令3,当前目录,父目录,根目录
c语言·c++·windows·visual studio
kyle~5 小时前
C/C++---动态内存管理(new delete)
c语言·开发语言·c++
知识分享小能手5 小时前
React学习教程,从入门到精通, React 新创建组件语法知识点及案例代码(11)
前端·javascript·学习·react.js·架构·前端框架·react
江团1io05 小时前
微服务雪崩问题与系统性防御方案
微服务·云原生·架构
LDelon5 小时前
iOS GitSubModule CocoaPod 制作私有源本地组件库
架构
Yuki’6 小时前
网络编程---UDP
c语言·网络·网络协议·udp
.YM.Z6 小时前
C语言——文件操作
c语言·文件操作