C语言嵌入式开发编程篇:typedef与回调函数
- C语言
- 2025-05-04
- 448热度
- 0评论
typedef关键字
C语言中的 typedef 是用于为已有的数据类型定义新名称(别名)的关键字。它并不创建新的数据类型,而是为现有类型提供更易读、更简洁的标识符,从而提高代码的可读性和可维护性。
typedef 的基本语法非常简单,其格式为:
typedef 原数据类型 新类型名;
例如:可以为基本数据类型int取一个别名Integer:
typedef int Integer;
为int定义别名后,在后续的代码中,就可以使用Integer来声明int类型的变量:
Integer a = 10;
这和直接使用int a = 10;的效果是完全一样的,在代码中使用Integer比使用int可以让代码阅读者更清晰地理解这个变量的作用。
typedef与函数指针
使用typedef可以定义函数指针类型:
// 定义函数指针类型 CompareFunc
typedef int (*CompareFunc)(const void*, const void*);
// 使用示例
int compare_ints(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
CompareFunc cmp = compare_ints; // 直接使用类型名
上述代码使用 typedef 创建了名为 CompareFunc 的函数指针类型,该类型指针指向的函数接受两个 const void* 参数并返回 int 值。然后定义了函数compare_ints(),将函数名赋值给函数指针。
使用 typedef 命名函数指针类型可以显著提升代码的可读性和灵活性,下面给出具体的示例。
示例1:简化复杂函数指针声明
没有 typedef 的写法:
int (*func1)(int, float); // 声明一个函数指针变量
int (*func_array[5])(int, float); // 声明包含5个函数指针的数组
使用 typedef 的写法:
typedef int (*MathFunc)(int, float); // 定义类型别名
MathFunc func1; // 直接声明变量
MathFunc func_array[5]; // 声明数组
好处:
避免重复书写复杂的函数指针语法。代码更简洁,类型意义更明确(MathFunc 比 int (*)(int, float) 更易理解)。
示例2:增强回调函数代码的可维护性
没有 typedef:
void register_callback(int (*callback)(const char*, int)) {
// 使用回调函数
}
调用时:
int my_callback(const char* s, int x) { /* ... */ }
register_callback(my_callback);
使用 typedef:
typedef int (*CallbackType)(const char*, int); // 定义类型别名
void register_callback(CallbackType callback) { // 参数类型清晰
// 使用回调函数
}
调用时:
CallbackType my_callback = /* ... */;
register_callback(my_callback);
好处:
函数参数类型一目了然,避免歧义。修改回调函数类型时只需修改 typedef 定义,无需逐个修改函数声明。
示例3:作为结构体成员
没有 typedef:
struct Calculator {
int (*add)(int, int); // 加法函数指针
int (*subtract)(int, int); // 减法函数指针
};
使用 typedef:
typedef int (*ArithmeticOp)(int, int); // 定义统一的运算操作类型
struct Calculator {
ArithmeticOp add; // 成员类型直接使用别名
ArithmeticOp subtract;
};
好处:
结构体成员类型清晰,明确表达设计意图。后续新增乘法、除法等操作时,只需扩展 ArithmeticOp 类型的使用。
示例4:函数返回函数指针
没有 typedef:
int (*get_operation(char op))(int, int) { // 返回类型难以阅读
if (op == '+') return &add;
else return &subtract;
}
使用 typedef:
typedef int (*Operation)(int, int); // 定义类型别名
Operation get_operation(char op) { // 返回类型简洁明了
if (op == '+') return &add;
else return &subtract;
}
好处:
函数返回值类型清晰,避免嵌套的指针语法。
任务调度器案例
下面实现一个任务调度器,主要实现任务的注册、执行和管理功能。
一、定义任务的状态枚举类型和任务结构体
// 任务状态枚举
typedef enum {
TASK_READY,
TASK_RUNNING,
TASK_BLOCKED,
TASK_FINISHED
} TaskStatus;
// 任务结构体
typedef struct Task {
TaskStatus status;
void (*taskFunction)(); // 任务函数指针
struct Task *next;
} Task;
上述代码使用 typedef 定义了TaskStatus枚举类型来表示任务的不同状态,同时定义了Task结构体,其中包含任务的状态、指向任务函数的指针taskFunction以及指向下一个任务的指针next 。这里的taskFunction就是一个函数指针,它指向具体的任务执行函数。
二、实现任务调度器的核心功能,包括任务注册和任务执行
// 任务链表头指针
Task *taskList = NULL;
// 注册任务
void registerTask(void (*task)()) {
Task *newTask = (Task *)malloc(sizeof(Task));
newTask->status = TASK_READY;
newTask->taskFunction = task;
newTask->next = taskList;
taskList = newTask;
}
// 执行任务
void executeTasks() {
Task *currentTask = taskList;
while (currentTask != NULL) {
if (currentTask->status == TASK_READY) {
currentTask->status = TASK_RUNNING;
currentTask->taskFunction(); // 通过函数指针调用任务函数
currentTask->status = TASK_FINISHED;
}
currentTask = currentTask->next;
}
}
在registerTask函数中,通过malloc函数分配内存创建一个新的任务节点,将传入的任务函数指针赋值给newTask->taskFunction,并将新任务节点插入到任务链表的头部。在executeTasks函数中,遍历任务链表,当找到处于TASK_READY状态的任务时,将其状态设置为TASK_RUNNING,然后通过函数指针currentTask->taskFunction调用任务函数,执行任务,任务执行完成后将状态设置为TASK_FINISHED 。
三、定义具体的任务函数并进行测试
// 具体任务函数1
void task1() {
printf("任务1正在执行\n");
}
// 具体任务函数2
void task2() {
printf("任务2正在执行\n");
}
int main() {
// 注册任务
registerTask(task1);
registerTask(task2);
// 执行任务
executeTasks();
return 0;
}
在main函数中,调用registerTask函数注册了两个任务task1和task2,然后调用executeTasks函数执行任务调度器,依次执行注册的任务 。
通过这个简易操作系统任务调度器的案例,可以看到 typedef 和函数指针的协同工作机制。typedef 为任务状态枚举和任务结构体定义了简洁的别名,提高了代码的可读性;而函数指针则使得任务调度器能够灵活地调用不同的任务函数,实现了任务的动态执行和管理。
中断回调案例
假设我们在一个基于 ARM Cortex-M3 内核的嵌入式系统中,使用 GPIO(通用输入输出)引脚来检测外部按键的按下事件,并通过中断回调函数进行处理。下面是具体的实现步骤和代码示例。
一、定义中断回调函数类型
使用typedef定义一个函数指针类型,用于表示中断回调函数。在这个案例中,中断回调函数没有返回值,也没有参数:
typedef void (*InterruptCallback)(void);
上述代码定义了一个名为InterruptCallback的函数指针类型,它指向的函数返回值为void,参数列表也为void。
二、初始化 GPIO 与中断配置
对GPIO 和中断进行初始化配置。这里假设使用的是 STM32 系列微控制器,相关代码如下:
#include "stm32f10x.h"
void GPIO_Interrupt_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO引脚为输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置EXTI中断线
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC中断优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
上述代码实现了 GPIO 引脚的初始化配置,将 PA0 引脚设置为浮空输入模式,并配置了外部中断线 EXTI0,使其在下降沿触发中断,同时还配置了 NVIC(嵌套向量中断控制器)的中断优先级。
三、注册中断回调函数
定义一个函数来注册中断回调函数。可以定义一个全局的函数指针变量,并提供一个函数来设置该指针:
InterruptCallback currentCallback = NULL;
void Register_Interrupt_Callback(InterruptCallback callback) {
currentCallback = callback;
}
上述代码定义了一个全局的InterruptCallback类型的指针变量currentCallback,初始化为NULL,并提供了Register_Interrupt_Callback函数用于注册中断回调函数,将传入的回调函数地址赋值给currentCallback。
四、编写中断服务函数
中断服务函数是在中断发生时由系统自动调用的函数,在该函数中需要调用注册的中断回调函数:
void EXTI0_IRQHandler(void) {
if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
if (currentCallback != NULL) {
currentCallback(); // 调用注册的中断回调函数
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
上述代码中,EXTI0_IRQHandler是外部中断线 EXTI0 对应的中断服务函数。在函数中,首先检查中断标志位是否置位,如果置位且存在已注册的中断回调函数,则调用该回调函数,最后清除中断标志位。
五、使用中断回调函数
在主函数中,可以进行如下操作来使用中断回调机制:
int main(void) {
GPIO_Interrupt_Init();
// 定义一个具体的中断回调函数
void Button_Pressed_Callback(void) {
// 在这里编写按键按下后的处理逻辑,例如点亮LED等
// 假设LED连接在PB5引脚
GPIO_SetBits(GPIOB, GPIO_Pin_5);
}
Register_Interrupt_Callback(Button_Pressed_Callback);
while (1) {
// 主循环可以执行其他任务
}
}
在上述主函数中,首先调用GPIO_Interrupt_Init函数进行 GPIO 和中断的初始化配置,然后定义了一个具体的中断回调函数Button_Pressed_Callback,该函数实现了按键按下后的处理逻辑(这里假设是点亮连接在 PB5 引脚的 LED),最后通过Register_Interrupt_Callback函数将该回调函数注册到系统中。主循环可以继续执行其他任务,当外部按键按下触发中断时,系统会自动调用中断服务函数,进而执行注册的中断回调函数。