一,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)来获取值;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就开始显现。使用表驱动法时需要关注两个问题:一是如何查表,从表中读取正确的数据;二是表里存放什么,如数值或函数指针。
掌握了上述基本方法,我们在设计更大更复杂的系统时,就可以适当重构:
- 增强可读性,消息如何处理从表中一目了然。
- 增强可扩展性。更容易修改,要增加新的消息,只要修改数据即可,不需要修改流程。
- 降低复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。
- 主干清晰,代码重用。
全局变量的使用原则:
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)。将具体定义放在源文件中可部分避免该问题。