C语言实现面向对象

时间:2023-01-02 11:18:49

C语言是一种通用的程序设计语言,几乎可以实现任何的程序设计范型,面向过程式、函数式、面向对象的等等,但是有些程序设计范型实现起来是不容易的,比如在C语言中实现面向对象的继承、多态等是相当不容易的,我们通常说一种语言支持某种程序设计范型是因为这种语言实现这种程序设计范型相当容易,比如在C++中实现面向对象是相当容易的,所以我们说C++是一种支持面向对象程序设计范型的程序设计语言。

 [封装性]

数据封装在C语言中还是比较容易实现的,主要是基于这样的考虑:在C语言中static的全局变量和static的函数只能在本文件范围内可见,这样我们就可以声明一些内部数据为static的,然后提供extern函数去访问数据,如:

module.h

[cpp] view plaincopyprint?
  1. #ifndef _MODULE_H_FLAG_   
  2. #define _MODULE_H_FLAG_   
  3.   
  4. typedef struct strval  
  5. {  
  6.   char* str;  
  7.   int len;  
  8. }strval;  
  9.   
  10. void init_str(const char* str);  
  11.   
  12. strval get_str(void);  
  13.   
  14. void free_str();  
  15.   
  16. void strval_destroy(strval* str);  
  17.   
  18. #endif  

结构体strval表示字符串,储存有字符串首地址和长度,几个extern函数定义如下:

module.cpp

 

结构体strval表示字符串,储存有字符串首地址和长度,几个extern函数定义如下:

module.cpp

[cpp] view plaincopyprint?
  1. #include "module.h"   
  2. #include <string.h>   
  3. #include <malloc.h>   
  4. #include <assert.h>   
  5.   
  6. // 全局静态变量 外部不能直接访问   
  7. static char* _data = NULL;  
  8. static int _data_len = 0;  
  9.   
  10.   
  11. void init_str(const char* str)  
  12. {  
  13.   _data_len = strlen(str);  
  14.   free_str();  
  15.   _data = (char*)malloc(_data_len + 1);  
  16.   assert(NULL != _data);  
  17.   strcpy(_data,str);  
  18. }  
  19.   
  20. void free_str()  
  21. {  
  22.   if (NULL != _data)  
  23.     free(_data);  
  24. }  
  25.   
  26. void strval_destroy(strval* str)  
  27. {  
  28.   if (NULL != str->str)  
  29.     free(str->str);  
  30. }  
  31.   
  32. strval get_str(void)  
  33. {  
  34.   strval temp;  
  35.   assert(NULL != _data);  
  36.   temp.str = (char*)malloc(_data_len + 1);  
  37.   strcpy(temp.str,_data);  
  38.   temp.len = _data_len;  
  39.   return temp;  
  40. }  


 

在这个例子中无论如何你不能直接访问内部数据_data,访问的接口为init_str,get_str等,其中的get_str()函数返回的不是内部数据_data,而是_data指向字符串的拷贝,也就是你不能直接修改_data指向的内容。但是无论如何这种封装性比不上C++的数据封装,顶多相当于C++中的private static 数据成员变量(但还是不一样的,只是类比),因为所有的函数共享同一块内存,在这个例子中就是_data以及_data指向的内存块。

这就引出了这种方式的弊端,如果在多线程应用中,他是线程不安全的,你必须想办法消除这种潜在危险,一个做法是线程同步,但这几乎总是导致性能下降——你几乎总是在等其他线程调用的结束,另一个做法是线程访问时拷贝,但这也会有一些问题,所以如果你想使用面向对象的特性,那就使用C++或者其他面向对象语言,当然如果你觉得性能与硬件相关更为紧迫的话,那你可以仍然使用C并忍受C的缺点(哈哈,有点像交女朋友。你可以享受她的优良品质,但你同时必须忍受她的缺点,因为人无完人)。

常规的C程序开发,都是高度模块化的,要不然代码管理会让你望而却步,不需要外部访问的函数就声明为static的吧,不同文件中的同名的static函数,是不同的,不会引起歧义的。

[继承与多态]

要说面向对象这一节就必须有,但是在C语言中实现继承与多态是很艰难的,但还是能够实现简单的特性的,你可以写一个框架来支持这种奢侈,手工的去建立一个virtual function table,以及用一些奇怪的宏去包装哪些丑陋的代码,……但这几乎就是一个噩梦…你懂得,我们应该适可而止,在用C语言进行程序设计时,尽量借鉴一些先进的设计方法,但不要过分追求某种形式,解决问题是根本,合理的运用C语言的特性,依然可以获得可读性强,模块化高的优秀程序的。而且在一个真正的应用开发时,不是只用一种语言的,往往我们可以选择C、C++、java或者C、C++、C#这样的组合,当然python加C、C++似乎很有吸引力(当然这种组合丧失的是run everywhe的特点了)。



C语言的对象化模型
面向对象的特征主要包括:
.封装,隐藏内部实现
.继承,复用现有代码
.多态,改写对象行为
采用C语言实现的关键是如何运用C语言本身的特性来实现上述面向对象的特征。

1.1  封装
封装是一种信息隐蔽技术,它体现于类的说明,是对象的重要特性。封装使数据和加工该数据的
方法(函数)封装为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性(对象
能接受哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能
力的算法)对用户是隐蔽的。封装的目的在于把对象的设计者和对象者的使用分开,使用者不必
知晓行为实现的细节,只须用设计者提供的消息来访问该对象。
在C语言中,大多数函数的命名方式是动词+名词的形式,例如要获取一个semaphore,会命名
成take_semaphore,重点在take这个动作上。面向对象编程中刚好相反,命名为rt_sem_take,即名词+动词的形式,重点在名词上,体现了一个对象的方法。另外对于某些方法,仅局限在对象内部使用,它们将采用static修辞把作用范围局限在一个文件的内部。通过这样的方式,把一些不想让用户知道的信息屏蔽在封装里,用户只看到了外层的接口,从而形成了面向对象中的最基本的对象封装实现。

一般属于某个类的对象会有一个统一的创建,析构过程。
.对象内存数据块已经存在,需要对它进行初始化 – rt_sem_init;
.对象内存数据块还未分配,需要创建并初始化 – rt_sem_create。
可以这么认为,对象的创建(create)是以对象的初始化(init)为基础的,创建动作相比较而言多了个
内存分配的动作。
相对应的两类析构方式:
.由rt_sem_init初始化的semaphore对象 – rt_sem_detach;
.由rt_sem_create创建的semaphore对象 – rt_sem_delete.

1.2  继承
继承性是子类自动共享父类之间数据和方法的机制。它由类的派生功能体现。一个类直接继承其
它类的全部描述,同时可修改和扩充。继承具有传递性。继承分为单继承(一个子类只有一父类)
和多重继承(一个类有多个父类,当前RT-Thread的对象系统不能支持)。类的对象是各自封闭的,
如果没继承性机制,则类对象中数据、方法就会出现大量重复。继承不仅支持系统的可重用性,而
且还促进系统的可扩充性。

类似的实现代码如下程序清单:
/*  父类  */
struct  parent_class
{
  int  a,  b;
  char  *str;
};

/*  继承于父类的子类  */
struct  child_class
{
  struct  parent class  p;
  int  a,  b;
};

/*  操作示例函数*/
void  func()
{
  struct  child_class  obj,  *obj_ptr;  /*  子类对象及指针  */
  struct  parent_class  *parent_ptr;  /*  父类指针  */
  obj_ptr  =  &obj;

  /*  取父指针  */
  parent_ptr  =  (struct  parent*)  &obj;

  /*  可通过转换过类型的父类指针访问相应的属性  */
  parent ptr->a  =  1;
  parent ptr->b  =  5;

  /*  子类属性的操作  */
  obj ptr->a  =  10;
  obj ptr->b  =  100;
}

在上面代码中,注意child_class结构中第一个成员p,这种声明方式代表child_class类型的数据中
开始的位置包含一个parent_class类型的变量。在函数func中obj是一个child_class对象,正像这个
结构类型指示的,它前面的数据应该包含一个parent_class类型的数据。在第21行的强制类型赋值
中parent_ptr指向了obj变量的首地址,也就是obj变量中的p对象。好了,现在parent_ptr指向的是
一个真真实实的parent类型的结构,那么可以按照parent的方式访问其中的成员,当然也包括可以
使用和parent结构相关的函数来处理内部数据,因为一个正常的,正确的代码,它是不会越界访
问parent结构体以外的数据。
经过这基本的结构体层层相套包含,对象简单的继存关系就体现出来了:父对象放于数据块的最
前方,代码中可以通过强制类型转换获得父对象指针。

1.3  多态
    对象根据所接收的消息而做出动作。同一消息为不同的对象接受时可产生完全不同的行动,这种
现象称为多态性。利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接受消息
的对象自行决定,如是,同一消息即可调用不同的方法。例如:抽象设备具备接口统一的读写接口。
串口是设备的一种,也应支持设备的读写。但串口的读写操作是串口所特有的,不应和其他设备
操作完全相同,例如操作串口的操作不应应用于SD卡设备中。
    多态性的实现受到继承性的支持,利用类继承的层次关系,把具有通用功能的协议存放在类层次
中尽可能高的地方,而将实现这一功能的不同方法置于较低层次,这样,在这些低层次上生成的
对象就能给通用消息以不同的响应。

对象模型采用结构封装中使用指针的形式达到面向对象中多态的效果,例如:
/*  抽象父类  */
struct  parent_class
{
  int  a;

  /*  反映不同类别属性的方法  */
  void  (*vfunc)(int  a);
}

/*  抽象类的方法调用  */
void  parent_class_vfunc(struct  parent_class  *self,  int  a)
{
  assert(self  !=  NULL);
  assert(slef->vfunc  !=  NULL);

  /*  调用对象本身的虚拟函数  */
  self->vfunc(a);
}

/*  继承自parent class的子类  */
struct  child_class
{
  struct  parent_class  parent;
  int  b;
};

/*  子类的构造函数  */
void  child_class_init(struct  child_class*  self)
{
  struct  parent_class*  parent;

  /*  强制类型转换获得父类指针  */
  parent  =  (struct  base_class*)  self;
  assert(parent  !=  NULL);
 
  /*  设置子类的虚拟函数  */
  parent->vfunc  =  child_class_vfunc;
}

/*  子类的虚拟函数实现  */
static  void   child class vfunc(struct  child class*self,  int  a)
{
  self->b  =  a  +  10;
}



更多资源文章参考:http://blog.csdn.net/kennyrose/article/details/7564105