C语言对象编程一:封装与抽象

文章来源:C语言对象编程第一弹:封装与抽象

前言

上次整理了一篇关于面向对象的笔记:《什么是面向对象?》。简单地分享了面向对象的一些基础知识。

C语言虽不是面向对象的语言,但也可以使用面向对象的思想来设计我们的程序。

C语言 + 面向对象的思想在我们嵌入式中使用得很广泛,主要优点就是能使我们的软件拓展性更好、更易读、更容易维护等。

因为这一块知识也比较重要,属于通用知识,所以打算分享几篇笔记与大家一起学习一下。

当然,C语言并不是面向对象的语言,要想完全实现与C++一样的一些面向对象的特性会比较难。所以我们分享的内容也面向基础、实用的为主。

本篇笔记分享的是:封装与抽象

封装与抽象

封装性是面向对象编程的三大特性(封装性、继承性、多态性)之一,但也是最重要的特性。封装+抽象相结合就可以对外提供一个低耦合的模块。

数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。

在C语言中,数据封装可以从结构体入手,结构体里可以放数据成员和操作数据的函数指针成员。当然,结构体里也可以只包含着要操作的数据。

下面以一个简单的实例作为演示。

设计一个软件模块,模块中要操作的对象是长方形,需要对外提供的接口有:

1、创建长方形对象;
2、设置长、宽;
3、获取长方形面积;
4、打印长方形的信息(长、宽、高);
5、删除长方形对象。

下面我们来一起完成这个demo代码。首先,我们思考一下,我们的接口命名大概是怎样的?其实这是有规律可循的,我们看RT-Thread的面向对象接口是怎么设计的:

在这里插入图片描述
在这里插入图片描述
我们也模仿这样子的命名形式来给我们这个demo的几个接口命名:

1、rect_create
2、rect_set
3、rect_getArea
4、rect_display
5、rect_delete

我们建立一个rect.h的头文件,在这里声明我们对外提供的几个接口。这时候我们头文件可以设计为:

#ifndef RECT_H
#define RECT_H

#ifdef _cplusplus
extern "c"{
#endif

/* 长方形结构体(类)*/
typedef struct _Rect
{
    char *object_name;
    int length;
    int width;
}Rect,*pRect;

/* 长方形对象操作 */
pRect rect_create(const char *object_name);
void rect_set(pRect rect, int length, int width);
int rect_getArea(pRect rect);
void rect_display(pRect rect);
void rect_delete(pRect rect);

#ifdef _cplusplus
}
#endif

#endif/* RECT_H */

这样做是没有什么问题的。可是数据隐藏得不够好,我们提供给外部用的东西要尽量简单。

我们可以思考一下,对于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 RECT_H
#define RECT_H

#ifdef _cplusplus
extern "c"{
#endif

typedef void* HandleRect

/* 长方形对象操作 */
HandleRect rect_create(const char *object_name);
void rect_set(HandleRect rect, int length, int width);
int rect_getArea(HandleRect rect);
void rect_display(HandleRect rect);
void rect_delete(HandleRect rect);

#ifdef _cplusplus
}
#endif

#endif/* RECT_H */

这里用到了void*,其为无类型指针,void *可以指向任何类型的数据。然后具体要操作怎么样的结构体可以在.c中实现:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rect.h"

/* HandleRect 句柄实际指向的结构体 */
typedef struct _Rect
{
    char *object_name;
    int length;
    int width;
}Rect,*pRect;

下面我们依次实现上述五个函数:

1、rect_create函数

/* 创建长方形对象 */
HandleRect rect_create(const char *object_name)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);

 /* 给rect结构体变量分配内存 */
 pRect rect = (pRect)malloc(sizeof(Rect));
 if (NULL == rect)
 {
  //free(rect);
  //rect = NULL;
  abort();
 }
 /* 给rect->object_name字符串申请内存 */
 rect->object_name = (char*)malloc(strlen(object_name) + 1);
 if (NULL == rect->object_name)
 {
  //free(rect->object_name);
  //rect->object_name = NULL;
  abort();
 }

 /* 给结构体各成员进行初始化 */
 strncpy(rect->object_name, object_name, strlen(object_name) + 1);
 rect->length = 0;
 rect->width = 0;
 
 return ((HandleRect)rect);
}

rect对象创建函数:首先分配内存,然后对rect结构体各个成员进行赋值操作,最后返回的是rect对象句柄。rect的object_name成员是个字符串,因此要单独分配内存。

2、rect_set函数

/* 设置长方形对象长、宽 */
void rect_set(HandleRect rect, int length, int width)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
 if (rect)
 {
  ((pRect)rect)->length = length;
  ((pRect)rect)->width = width;
 }
}

3、rect_getArea函数

/* 获取长方形对象面积 */
int rect_getArea(HandleRect rect)
{
 return ( ((pRect)rect)->length * ((pRect)rect)->width );
}

4、rect_display函数

/* 打印显示长方形对象信息 */
void rect_display(HandleRect rect)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
 if (rect)
 {
  printf("object_name = %s\n", ((pRect)rect)->object_name);
  printf("length = %d\n", ((pRect)rect)->length);
  printf("width = %d\n", ((pRect)rect)->width);
  printf("area = %d\n", rect_getArea(rect));
 }
}

5、rect_delete函数

void rect_delete(HandleRect rect)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
 if (rect)
 {
  free(((pRect)rect)->object_name);
  free(rect);
  ((pRect)rect)->object_name = NULL;
  rect = NULL;
 }
}

rect对象删除函数:主要是对创建函数中的malloc申请的内存做释放操作。

可以看到这五个对象接口主要包含三类:创建对象函数、操作函数、删除对象函数。这里的操作函数就是rect_set函数、rect_getArea函数与rect_display函数,当然还可以有其它更多的操作函数。

操作函数的特点是至少需要传入一个表示对象的句柄,在函数的内部再做实际数据结构的转换,然后再进行相应的操作。

6、测试程序:

#include <stdio.h>
#include <stdlib.h>
#include "rect.h"

int main(void)
{
 HandleRect rect = rect_create("rect_obj");  // 创建Rect对象句柄
 rect_set(rect, 20, 5);         // 设置     
 rect_display(rect);            // 打印显示 
 rect_delete(rect);             // 删除Rect对象句柄 
 
 return 0;
}

运行结果:
在这里插入图片描述

在基于对象的编程中,封装性是最基础也最重要的内容。其对象主要包含两方面内容:属性方法

在基于C语言的对象编程中,可以使用句柄来表示对象,即句柄指向的数据结构的成员代表对象的属性,实际操作句柄的函数则表示对象的方法。

版权归原作者所有,如有侵权,请联系删除。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值