封装与抽象
封装性是面向对象编程的三大特性(封装性、继承性、多态性)之一,但也是最重要的特性。封装+抽象相结合就可以对外提供一个低耦合的模块。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
在C语言中,数据封装可以从结构体入手,结构体里可以放数据成员和操作数据的函数指针成员。当然,结构体里也可以只包含着要操作的数据。
下面以一个简单的实例作为演示。
设计一个软件模块,模块中要操作的对象是长方形,需要对外提供的接口有:
1、创建长方形对象;
2、设置长、宽;
3、获取长方形面积;
4、打印长方形的信息(长、宽、高);
5、删除长方形对象。
下面我们来一起完成这个demo代码。首先,我们思考一下,我们的接口命名大概是怎样的?其实这是有规律可循的,我们看RT-Thread的面向对象接口是怎么设计的
我们也模仿这样子的命名形式来给我们这个demo的几个接口命名:
1、shape_create
2、shape_set
3、shape_getArea
4、shape_display
5、shape_delete
我们建立一个rect.h的头文件,在这里声明我们对外提供的几个接口。这时候我们头文件可以设计为:
typedef struct _fun_ptr
{
int (*area)(void);
}fun_ptr;
typedef struct _Shape
{
char* object_name;
int length;
int width;
fun_ptr fptr;
}Shape, *pShape;
/* 对象操作 */
HandleShape shape_create(const char* object_name);
void shape_set(HandleShape shape, int length, int width);
int shape_getArea(HandleShape shape);
void shape_display(HandleShape shape);
void shape_delete(HandleShape shape);
这样做是没有什么问题的。可是数据隐藏得不够好,我们提供给外部用的东西要尽量简单。
我们可以思考一下,对于C语言的文件操作,C语言库给我们提供怎么样的文件操作接口?如:
FILE *fopen(const char *pathname, const char *mode);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
我们会创建一个文件句柄(描述符),然后之后只要操作这个文件句柄就可以,我们不用关心FILE具体是怎么实现的。
我们也可以创建我们的对象句柄,对外提供的头文件中只需暴露我们的对象句柄,不用暴露具体的实现。以上头文件rect.h代码可以修改为:
#ifndef __SHAPE_H
#define __SHAPE_H
#ifdef __cplusplus
extern "C" {
#endif
/* 对象句柄 */
typedef void* HandleShape;
/* 对象操作 */
HandleShape shape_create(const char* object_name);
void shape_set(HandleShape shape, int length, int width);
int shape_getArea(HandleShape shape);
void shape_display(HandleShape shape);
void shape_delete(HandleShape shape);
#ifdef __cplusplus
}
#endif
#endif
这里用到了void*,其为无类型指针,void *可以指向任何类型的数据。然后具体要操作怎么样的结构体可以在.c中实现:
#include
#include
#include
#include "CShape.h"
typedef struct _fun_ptr
{
int (*area)(void);
}fun_ptr;
typedef struct _Shape
{
char* object_name;
int length;
int width;
fun_ptr fptr;
}Shape, *pShape;
下面我们依次实现上述五个函数:
HandleShape shape_create(const char* object_name)
{
printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
/* 给shape结构体变量分配内存 */
pShape shape = (pShape)malloc(sizeof(Shape));
if (NULL == shape)
{
printf("shape memory malloc failed!\n");
abort();
}
/* 给shape->object_name字符串申请内存 */
shape->object_name = (char*)malloc(strlen(object_name) + 1);
if (NULL == shape->object_name)
{
printf("shape->object_name memory malloc failed!\n");
abort();
}
/* 给结构体各成员进行初始化 */
strncpy(shape->object_name, object_name, strlen(object_name) + 1);
shape->length = 0;
shape->width = 0;
return ((HandleShape)shape);
}
void shape_set(HandleShape shape, int length, int width)
{
printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
if (shape)
{
((pShape)shape)->length = length;
((pShape)shape)->width = width;
}
}
int shape_getArea(HandleShape shape)
{
return ( ((pShape)shape)->length * ((pShape)shape)->width );
}
void shape_display(HandleShape shape)
{
printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
if (shape)
{
printf("object_name = %s\n", ((pShape)shape)->object_name);
printf("length = %d\n", ((pShape)shape)->length);
printf("width = %d\n", ((pShape)shape)->width);
printf("area = %d\n", shape_getArea(shape));
}
}
void shape_delete(HandleShape shape)
{
printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
if (shape)
{
// free(((HandleShape)shape)->object_name);
// ((HandleShape)shape)->object_name = NULL;
// free(shape);
// shape = NULL;
}
}