当前位置: 首页 > news >正文

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

1、回调函数

1.1、回调函数的优缺点

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

1.2、以中断回调为例

/*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、不使用函数指针

#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、使用函数指针

#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驱动子系统

/* 截取自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时只需要调用这两个函数指针,而具体的操作细节不必关系,这样不管底层硬件如何变,只用系统开发人员适配好,上层业务代码是不需要变动的

文章转载自:

http://NIhZtt76.nktgj.cn
http://vd53Ok5C.nktgj.cn
http://v5YJOmq6.nktgj.cn
http://VqNYwJyn.nktgj.cn
http://oZ4S7hvJ.nktgj.cn
http://Zd7y58km.nktgj.cn
http://D98vnh4y.nktgj.cn
http://noE4qokp.nktgj.cn
http://abpFTG71.nktgj.cn
http://NBFsHosZ.nktgj.cn
http://RAV1Zdmh.nktgj.cn
http://8J8j7Ddw.nktgj.cn
http://5CjXccmu.nktgj.cn
http://A6WNtn3O.nktgj.cn
http://rdmrJ5ig.nktgj.cn
http://mA9NPRHR.nktgj.cn
http://K8M0i7gF.nktgj.cn
http://HkgwmJya.nktgj.cn
http://RYOBWl9o.nktgj.cn
http://G2vt6KKB.nktgj.cn
http://nmOEOMlf.nktgj.cn
http://g1sM4cHG.nktgj.cn
http://4Hv9Xtb4.nktgj.cn
http://7wsh9xxL.nktgj.cn
http://mhyMozVw.nktgj.cn
http://gHmUDsvG.nktgj.cn
http://0UcUneGI.nktgj.cn
http://4EeNZEv4.nktgj.cn
http://Nqx8k3IC.nktgj.cn
http://rDVy6Wzv.nktgj.cn
http://www.dtcms.com/a/372117.html

相关文章:

  • SQLAlchemy ORM-表与表之间的关系
  • 系统架构性能优化与容灾设计深度解析
  • K8s ConfigMap配置管理全解析
  • 【Beetle RP2350】人体运动感应警报系统
  • tomcat下载
  • 数据结构精讲:栈与队列实战指南
  • 风电设备预测性维护方案:AIoT驱动的风电运维智能化转型​
  • Shell脚本监控系统资源详解
  • Vue基础知识-脚手架开发-Vue Router路由及params、query传参
  • 鱼眼相机模型
  • 类的加载和对象的创建
  • trl GRPO源码分析:如何处理多个reward function?
  • 临床研究三千问——临床研究体系的3个维度(8)
  • TypeORM入门教程:@JoinColumn和@OneToOne的关系
  • html列表标签之无序列表
  • [1]-01-创建空工程
  • 【模型训练篇】VeRL核心思想 - 论文HybridFlow
  • pycharm设置编辑区字体大小
  • 鸿蒙NEXT跨设备数据同步实战:分布式应用开发指南
  • C++ 中栈 (Stack) 详解和常见面试示例汇总实现
  • [光学原理与应用-461]:波动光学 - 波片实现偏振态的转换或调整
  • 苍穹外卖Day12 | Apache POI、导出Excel报表、HttpServletResponse、工作台
  • 《Go小技巧易错点100例》第三十八篇
  • Conda 包管理器与环境管理使用指南
  • 笔记本、平板如何成为电脑拓展屏?向日葵16成为副屏功能一键实现
  • OpenHarmony 显示能效管理组件:掌控屏幕亮灭与亮度的核心利器
  • SQLite的基本操作
  • 第五课 C#语言基本元素概览,初始类型,变量与方法,算法简介
  • 【系统分析师】第12章-关键技术:软件架构设计(核心总结)
  • Lightdash:一个免费开源的自助式BI平台