C语言--结构体回调函数示例

本文介绍了C语言编程中的三种实用技巧:使用结构体对象数组、函数指针和回调函数。通过这些技巧,可以提高代码的可读性和可扩展性,简化复杂系统的开发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,component结构体对象数组

说明:模拟一个小型系统,由两个组件组成,针对不同的组件类型,调用不同的任务处理函数。

#include <stdio.h>

typedef enum com_type {
    TYPE_1 = 0,
    TYPE_2,
    TYPE_3,
} com_type_t;

struct component_provider {
    com_type_t com_type;
    const char* com_name;
};

static const struct component_provider components[] = {
    {
        .com_type = TYPE_1,
        .com_name = "NAME-1",
    },
    {
        .com_type = TYPE_2,
        .com_name = "NAME-2",
    },
};

void create_task1() {
    printf("task1 dev_int!\n");
    printf("task1 worker start!\n");
}

void create_task2() {
    printf("task2 dev_int!\n");
    printf("task2 worker start!\n");
}

int main() {
    int max_providers = sizeof(components) / sizeof(struct component_provider);
    const struct component_provider* providers = components;

    for (int i = 0; i < max_providers; i++) {
        if (providers[i].com_type == TYPE_1) {
            create_task1();
        } else if (providers[i].com_type == TYPE_2) {
            create_task2();
        }
    }
    return 0;
}

二,component函数指针

假设后续组件不断增加,if else会越来越多,那么我们可以抽象出公共的函数代码,利用函数指针实现抽象后的逻辑,效果如下:

#include <stdio.h>

typedef enum com_type {
    TYPE_1 = 0,
    TYPE_2,
    TYPE_3,
} com_type_t;

typedef struct component_provider {
    com_type_t com_type;
    const char* com_name;
    void (*dev_init)(void);
    void (*worker_start)(void);
} comp;

void dev_start1(void) {
    printf("task1 dev_int!\n");
}

void dev_start2(void) {
    printf("task1 dev_int!\n");
}

void worker_start1(void) {
    printf("task1 worker start!\n");
}

void worker_start2(void) {
    printf("task2 worker start!\n");
}

static const comp g_comps[] = {
    {TYPE_1, "NAME-1", dev_start1, worker_start1},
    {TYPE_2, "NAME-2", dev_start2, worker_start2},
};

void create_task(const comp *com) {
    com->dev_init();
    com->worker_start();
}

int main() {
    int com_cnt = sizeof(g_comps) / sizeof(comp);
    const comp* comp_ops = g_comps;

    for (int i = 0; i < com_cnt; i++) {
        create_task(&comp_ops[i]);
    }
    return 0;
}

三,callback函数

假设我们希望系统运行时,各组件调用上层的check函数检查,那么就是回调的用法:


#include <stdio.h>

typedef enum com_type {
    TYPE_1 = 0,
    TYPE_2,
    TYPE_3,
} com_type_t;

typedef struct component_provider {
    com_type_t com_type;
    const char* com_name;
    void (*dev_init)(void);
    void (*worker_start)(void);
} comp;

/* callback 定义 */
typedef void (*check_cb)(void);
check_cb check_cb_t = NULL;

void check_cb_register(check_cb cb) {
    check_cb_t = cb;
}

void dev_start1(void) {
    printf("task1 dev_int!\n");
}

void dev_start2(void) {
    printf("task1 dev_int!\n");
}

void worker_start1(void) {
    printf("task2 worker start!\n");
    if (check_cb_t) {
        check_cb_t();
    }
}

void worker_start2(void) {
    printf("task2 worker start!\n");
    if (check_cb_t) {
        check_cb_t();
    }
}

static const comp g_comps[] = {
    {TYPE_1, "NAME-1", dev_start1, worker_start1},
    {TYPE_2, "NAME-2", dev_start2, worker_start2},
};

void create_task(const comp* com) {
    com->dev_init();
    com->worker_start();
}

void check(void) {
    printf("callback check finished!\n");
}

int main() {
    int com_cnt = sizeof(g_comps) / sizeof(comp);
    const comp* comp_ops = g_comps;

    check_cb_register(check);

    for (int i = 0; i < com_cnt; i++) {
        create_task(&comp_ops[i]);
    }
    return 0;
}

上述c文件只是为了演示函数指针、回调的基本用法。上升到方法论其实可以引出表驱动法(Table-Driven Approach)的基本概念。简而言之就是用查表的方法获取数据。此处的“表”通常为数组,但可视为数据库的一种体现。

具体到编程方面,在数据不多时可用逻辑判断语句(if…else或switch…case)来获取值;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就开始显现。使用表驱动法时需要关注两个问题:一是如何查表,从表中读取正确的数据;二是表里存放什么,如数值或函数指针。

掌握了上述基本方法,我们在设计更大更复杂的系统时,就可以适当重构:

  • 增强可读性,消息如何处理从表中一目了然。
  • 增强可扩展性。更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。
  • 降低复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
  • 主干清晰,代码重用。

C语言表驱动法编程实践
C语言头文件组织与包含原则

全局变量的使用原则:
1)若全局变量仅在单个源文件中访问,则可将该变量改为该文件内的静态全局变量;
2)若全局变量仅由单个函数访问,则可将该变量改为该函数内的静态局部变量;
3)尽量不要使用extern声明全局变量,最好提供函数访问这些变量。直接暴露全局变量是不安全的,外部用户未必完全理解这些变量的含义。
4)设计和调用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题

  • 避免包含重量级的平台头文件,如windows.h或d3d9.h等。若仅使用该头文件少量函数,可extern函数到源文件内
  • 头文件中若能前置声明(亦称前向声明[5]),就不要包含另一头文件。仅当前置声明不能满足或过于麻烦时才使用include,如此可减少依赖性方面的问题。

结构体类型S在声明之后定义之前是一个不完全类型(incomplete type),即已知S是一个类型,但不知道包含哪些成员。不完全类型只能用于定义指向该类型的指针,或声明使用该类型作为形参指针类型或返回指针类型的函数。指针类型对编译器而言大小固定(如32位机上为四字节),不会出现编译错误。

假设先后定义两个结构A和B,且两个结构需要互相引用。在定义A时B还没有定义,则要引用B就需要前向声明结构B(struct B;)。示例如下:

1 typedef BOOL (*func)(const DefStruct *ptStrt);
2 
3 typedef struct DefStruct_t{
4     int i;
5     func f;
6 } DefStruct;

如上在DefStruct中使用回调函数func声明,这样交叉引用必然编译报错。进行前向声明即可:

1 typedef struct DefStruct_t DefStruct;
2 typedef BOOL (*func)(const DefStruct *ptStrt);
3 
4 struct DefStruct_t{
5     int i;
6     func f;
7 };

注意,在前向声明和具体定义之间涉及标识符(变量、结构、函数等)实现细节的使用都是非法的。若函数被前向声明但未被调用,则编译和运行正常;若前向声明函数被调用但未被定义,则编译正常但链接报错(undefined reference)。将具体定义放在源文件中可部分避免该问题。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

造夢先森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值