目录

面向对象模块设计的 C 语言实现方法

一、前言

网上的面向对象编程文章往往是描述面向对象的思维(看了之后反而是“云里雾里”、很虚的感觉),至于如何使用代码来编写对象基本上没提及。那我们如何使用 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、最后“清除占用标志”

在“占有”期间,如果此时执行关闭操作,则会返回出错或忙!表明对象正打开中(打开操作还没完成),不能进行关闭操作!