一、前言
网上的面向对象编程文章往往是描述面向对象的思维(看了之后反而是“云里雾里”、很虚的感觉),至于如何使用代码来编写对象基本上没提及。那我们如何使用 C 语言来编写面向对象模块呢?
二、面向对象模块的设计导向
一、面向对象设计基本概念
面向对象编程 OOP(Object Oriented Programming)三个基本特征:封装、继承、多态。
封装:根据职责将属性(参数)和方法(行为)封装到一个抽象的类中。
继承:指可以让某个类型的对象获得另一个类型的对象的属性的方法。
多态:以封装和继承为前提,相同的方法调用不同的子类对象,产生不同的执行结果。
二、面向对象设计最终目标
● 在同一应用工程中,不需要增加、修改、复制代码,一份代码可“最大限度”
复用。
● 例如:一个键盘对象模块,应用程序可以使用它“最大限度”
地创建出多个键盘应用。
三、面向对象设计实施方法
1、总设计的原则为共性业务由对象模块处理;个性业务由应用模块处理。
2、对象模块只有操作代码而没有全局变量(相当于一台制作包子机器);
3、应用模块提供全局变量(对象结构体)(相当于制作包子的原材料)。
4、对象结构体格式由对象模块定义,包括:属性(参数),方法(行为)。
5、属性(参数)主要是对相关功能参数描述,它通过变量来描述功能参数;
6、方法(行为)则是与上下层程序对接关联,它通过函数指针来关联操作。
7、关于方法(行为)的函数指针:用于通知上层程序和调用下层硬件驱动。
8、对象模块为应用模块提供业务操作函数,例如:读、写这类操作函数。
四、小结
“封装、继承、多态”是面向对象编程的思维,是“虚体”;“属性、方法”是面向对象编程的实现方法,是“实体”。使用 C 语言编程时,“属性”则是通过变量来描述功能参数,“方法”则是通过函数指针来关联上下层程序,最后由对象模块为应用层提供业务操作函数。
三、面向对象模块的组织结构
第一类(主要针对静态固有的软硬件资源,例如:COM串口、文件操作等)
1、打开操作(配置属性 → 注册方法 → 打开操作)
2、关闭操作
3、读操作
4、写操作
5、其它操作
一、例如本人编写的红外发送模块的【打开操作】:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//红外发送模块提供的业务操作函数
extern int xt_irsend_open(xt_irsend_obj_t *p_ob);
extern int xt_irsend_close(xt_irsend_obj_t *p_ob);
extern int xt_irsend_send(xt_irsend_obj_t *p_ob, const uint16_t *pd, uint16_t size, uint8_t xus, uint8_t khz);
xt_irsend_obj_t app_irsend_obj; //声明红外发送服务对象
//配置属性
app_irsend_obj.end_us = 100000; //【属性】发完1帧红外码后强行插入间隔时间(单位:us)
app_irsend_obj.ir_khz = 38; //【属性】红外载波频率(预设)(单位:KHz)
app_irsend_obj.ir_num = 0; //【属性】红外发送通道编号(<IRSEND_SUM)
app_irsend_obj.send_buf_size = sizeof(app_irsend_buf); //【属性】红外发送缓冲大小(单位:字节)
app_irsend_obj.p_send_buf = app_irsend_buf; //【属性】红外发送缓冲
//注册方法
app_irsend_obj.p_irsend_open_fn = app_irsend_open_cb; //【应用服务】注册红外发送服务[打开]操作成功的协同回调
app_irsend_obj.p_irsend_close_fn = app_irsend_close_cb; //【应用服务】注册红外发送服务[关闭]操作成功的协同回调
app_irsend_obj.p_irsend_complete_fn = app_irsend_complete_cb; //【应用服务】注册红外发送刚刚[完成]的通知回调
app_irsend_obj.p_hw_open_fn = 0; //【硬件服务】注册硬件驱动程序(0:本应用不提供硬件驱动程序)
//打开对象
if (xt_irsend_open(&app_irsend_obj) < 0)
{
rt_kprintf("xt_irsend_open return error!\r\n");
}
|
备注:因个人喜好及习惯问题,配置属性和注册方法直接对对象赋值,没有提供函数操作(例如初始或注册操作函数:xt_irsend_attribute_init()、xt_irsend_method_reg()
)!注意只有打开对象前才这样操作,之后绝对不能直接修改对象数据!
二、例如网友写的类 rt_thread 操作系统【设备管理】:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
typedef struct cola_device cola_device_t;
struct cola_device_ops
{
int (*init) (cola_device_t *dev);
int (*open) (cola_device_t *dev, int oflag);
int (*close) (cola_device_t *dev);
int (*read) (cola_device_t *dev, int pos, void *buffer, int size);
int (*write) (cola_device_t *dev, int pos, const void *buffer, int size);
int (*control)(cola_device_t *dev, int cmd, void *args);
};
struct cola_device
{
const char *name; //设备名称
struct cola_device_ops *dops; //设备操作
struct cola_device *next; //设备链表
};
//1.1.设备注册----------
int cola_device_register(cola_device_t *dev);
//2.1.设备查找----------
cola_device_t *cola_device_find(const char *name);
//2.2.设备读------------
int cola_device_read(cola_device_t *dev, int pos, void *buffer, int size);
//2.3.设备写------------
int cola_device_write(cola_device_t *dev, int pos, const void *buffer, int size);
//2.4.设备控制----------
int cola_device_ctrl(cola_device_t *dev, int cmd, void *arg);
//一、上电初始化设备
void led_register(void)
{
led_gpio_init(); //网友的案例中是直接初始硬件,没有使用设备驱动来初始化硬件,也没有做[打开][关闭][读][写]服务
led_dev.dops = &ops; //static struct cola_device_ops ops = { .control = led_ctrl }; ←───────┘
led_dev.name = "led";
cola_device_register(&led_dev);
}
//二、应用层使用设备
void app_main(void)
{
app_led_dev = cola_device_find("led");
while (1)
{
cola_device_ctrl(app_led_dev, LED_TOGGLE, 0);
delay_ms(500);
}
}
|
设备管理从更高的角度来设计对象,可以统一所有硬件操作函数,统一所有硬件管理,非常值得我们学习和使用的面向对象编程!
第二类(主要针对可动态复用的软硬件资源,例如:抽象设备、信号量等)
1、创建操作(申请内存 → 初始化 → 返回句柄)
2、删除操作
3、读操作
4、写操作
5、其它操作
例如 RT-thread 实时操作系统的信号量【创建操作】:
1
2
3
4
5
6
|
rt_sem_t p_sem = RT_NULL;
//创建对象
if ((p_sem = rt_sem_create("sem", 0, RT_IPC_FLAG_FIFO)) == RT_NULL)
{
rt_kprintf("rt_sem_create return error!\r\n");
}
|
四、面向对象模块的内部处理
一、静态面向对象的开关互斥处理
因为对象是一个大数据综合体,每一步的操作必定出现临界问题,也就是共享资源竞争问题。其中打开与关闭对象的处理原则如下:
1、首先“判断是否存在”
2、接着“先占有再使用”
3、然后“进行相关操作”
4、最后“清除占用标志”
在“占有”期间,如果此时执行关闭操作,则会返回出错或忙!表明对象正打开中(打开操作还没完成),不能进行关闭操作!