赞
踩
C语言原本是面象过程编程的一个很好的例子,但却是应用非常广泛的一门编程语言,特别是在嵌入式领域,更是占据着不可替代的位置;另一方面,面向对象分析、设计、编程等概念已经被广泛接受,面向对象最大的优点就是使日后的维护变得容易,主要体现在软件架构设计和代码的解耦上,如果你按照“高内聚低耦合”的原则设计了一个好的系统,虽然代码量不一定减少,甚至还会增加,但是后期的维护成本会大大降低。在实际项目中,维护成本恰恰又是在软件的整个生命周期中所占比例最大的一部分,所以将面向对象的概念引入到C语言编程过程中具有重要的现实意义。
当然,你可以会说要想使用C语言进行面向对象的编程可以使用ObjC(Objective C),但支持它的编译器太少,特虽是在嵌入式领域,基本没有编译器支持ObjC。下面我们讨论一下使用ANSI标准的C语言进行面向对像的编译。
一、面向对象的概念在C语言中的实现的可能性讨论
封装、继承和多态是面向对象编程最基本也是最核心的三个概念,我们分别讨论它们在C语言中实现的可行性。
对于封装,C语言不支持C#、Java或C++等面各对像编程语言的类(class),但是在C语言中可以实现struct,这个与类非常接近,而在C++中,struct与class实际上并没有本质区别,只是struct的成员的访问权限默认为public,而class成员的默认访问权限为private。封装的产物就是类,其实例便是对象,而对象应该是属性和行为(方法)的集合,C语言能否支持这两个要素呢?
首先,对于属性,struct内部可以包含任何类型的数据成员作为对象的属性;可是struct内部不能有函数成员,这对于使对象包含期行为集好像有点儿困难。但是,别忘了,C语言有函数指针,函数指针可以指向任何一个函数,我们如果在struct内定义一系列的函数指针,然后将它们指向相应的函数不就可以了?但是对于数据和方法的隐藏,C语言真是无能为力了,它不能使用private关键字控制客户代码对struct成员的访问,这一点我们可以通过事先的约定来人为地实现该功能,后面的章节中我们将讨论这个问题。
然后,对于继承。C语言不支持继承,不能实现面向对象中的所谓"is-a"关系,但C语言的struct完全可以实现组合(composition),即所谓"has-a"关系。这样,我样可以先定义一个“父”struct,然后再定义一个子struct,在子struct中再添加子struct特有的属性或方法指针,这样基本可以模拟继承的概念。
最后,就是多态。多态包括两个方面,一个是静态的多态,另一个是动态的多态。静态的多态就是指函数重载,这个在C语言中是根本不支持的,无法实现,也很难模拟;动态的多态就是指虚函数了,这在C#、Java、C++中都有相应的内置关键字支持,而在C语言中连继承都没有,更不用说虚函数了,但类似的功能我们可以模拟。如何模拟动态的多态呢?我们上面说了,C语言中的struct具有函数指针,函数指针是一个变量当然可以动态更改设置了,我们就可以根据该结构的类型动态地进行装载函数。
二、C语言模拟面向对象的实现
上节讨论了C语言模拟面向对象编程的可行性,已经证实可以模拟面向对象编程的多数据概念,下面我们以一个简单的实例来演示如何实现。
(一)类的定义和继承的模拟
我们以很多书中都用的一个Shape、Circle和Rectangle的例子,例如我们已经通过UML建模,三个类组成的类图如下:

C语言实现代码如下:
Code List (1)
ABSTRACT struct Shape
{
PUBLIC ABSTRACT double (*getArea)(void* const shape);
PUBLIC ABSTRACT double (*print)(void* const shape);
};
struct Circle EXTERNS(Shape)
{
PUBLIC struct Shape shape;
PRIVATE double Radius;
PUBLIC double (*getRadius)(struct Circle* const cicle);
};
struct Rectangle EXTERNS(Shape)
{
struct Shape shape;
PRIVATE double Length;
PRIVATE double Width;
PUBLIC double (*getLength)(struct Rectangle* const rect);
PUBLIC double (*getWidth)(struct Rectangle* const rect);
};
这里的PRIVATE,PUBLIC的定义如下:
Code List(2):
#define PUBLIC
#define PRIVATE
#define ABSTRACT
#define EXTERNS(super)
仅仅向程序员声明这些成员的声明方式,因为C语言本身不支持,这里是我们的约定,假设程序员看到这些标识符时将按照它们的语义使用这些函数。从上面的代码我们可以清楚地明白类的各个类的关系,以及方法及属性的使用方法。这里我们模拟了Java的方式,还使用了Java的关键字EXTERNS,当然这里我们用大写表示是用的宏定义,为了看着更清楚我们可以把这些“关键字”写成小写,更像是面向对象的程序设计语言,实际使用中可以使用小写,这里为了显示更醒目还是使用了大写。
(二)创建对象
上节我们定义了三个“类”,这三个类的对象如何创建呢?按照C语言的编程方式,当然可以定义相应的变量,用于操作数据,但是现在要模拟的是面向对象的编程,这们应该使编程方式更像面向对象的编程。显然,不可能使用new关键字创建对象,也没有构造函数可以使用。注意到C语言是区分大小写的,可以调用与类名字相同但全部小写的函数名作为模拟构造函数,再定义一个NEW关键字
Code List(3)
#define NEW
Rectangle* rec = NEW rectangle(12,25);
但如果我们要创建几个带有不同参数的构造函数,这种方法就不好用了,因为C语言不允许我们使用函数重戴。但另一种方法可以躲避这个问题,那就是使用类工厂模式(Factory Patern),通过该模式的使用躲避构造函数的问题,比如想使用这样的方式创建对象。
Code List(4)
ShapeFactory.createRectangle(12,25);
ShapeFactory.createCircle(7.12);
ShapeFactory.createSquareRectangle(11.3);
这样看起来就更像是面向对象编程了。但Rectangle* rec的定义还是不像是面向对象编程,我们这样处理一下,定义一个IRectangle,好像是一个接口,定义如下:
typedef struct Rectangle* IRectangle;
这样以后再定义Rectangle的指针,就可以直接使用
IRectangle rec;
掩饰掉了C语言的指针,客户代码看起来更简洁。下面就定义createCircle方法
Code List(5):
/* circle
*
*/
static void installCircleMethods(ICircle c);
static ICircle createCircle(double radius)
{
ICircle c = (ICircle)malloc(sizeof(struct Circle));
c->Radius = radius;
installCircleMethods(c);
return c;
}
static double getCircleArea(ICircle c)
{
return 3.14 * c->Radius * c->Radius;
}
static void printCircle(ICircle c)
{
printf("Circle:radius = %g/n", c->Radius);
}
static double getCircleRadius(ICircle c)
{
return c->Radius;
}
static void installCircleMethods(ICircle c)
{
((IShape)c)->getArea = getCircleArea;
((IShape)c)->print = printCircle;
c->getRadius = getCircleRadius;
}
在这里可以看到对象的方法是通过函数installCircleMethods()在手动添加上的,这样,客户端代码就可以通过createCircle函数创建ICircle对象了。
Code List(6):
c = ShapeFactory.createCircle(3.5);
printf("area of c=%g/n", c->shape.getArea(c));
((IShape)c)->print(c);
printf("Radius of c=%g/n",c->getRadius(c));
(三)多态
实际上,在Code List(1)中,我们已经使用了“虚函数”的模拟,也就是实现了动态的多态,而对于静态的多态--函数名重载,在C语言中着实没法实现,因为在C语言中不支持两个函数的函数名相同。
实现了Code List (1)中定义的结构类型,我们就可以全用下面的代码进行测试:
Code List(7)
int i;
IShape shapes[4];
ICircle c;
shapes[0] = ShapeFactory.createCircle(3.5);
shapes[1] = ShapeFactory.creatRectangle(3.2, 1.7);
shapes[2] = ShapeFactory.creatRectangle(7,5);
shapes[3] = ShapeFactory.createCircle(8.2);
for(i=0; i<4; i++)
{
(shapes[i])->print(shapes[i]);
}
IShape是使用指针定义的接口,但这里必须注意,我们要使上面的代码Code List (7)能够正常运行,一个很重要的前提就是“派生类(子类)”Rectangle 和Circle中Shape 变量的位置必须是在第一位,如果再从这两个结构派生新的数据库型,也要将这两种类型的变量放在“子类”的最开始,否则Code List(7)将不能得到期望的结果,依次类推。
三、结论
本文使用C语言模拟了面向对象程序设计语言中的三个重要概念封装、继承和多态,而且使用指针模拟了面向对象程序设计语言中的“接口”的概念,并定义了相应的宏作为面向对象扩展的关键字,其本把C“改造”成为一种面向对象的程序设计语言。
而实际上,程序设计语言的语法就是一种规则,C和JAVA的面向对象的支持是编译器强制执行的一套语言,而本文定义的一些规则是没有编译器强制执行,而是靠程序员遵守的一套规则,只要遵守这些约定,建立在此套约定之上的C语言但基本成为一种面向对象的话言。
本文中所说的约定,为C语言编程人员提供了一种面向对象编程的方法,使面向对象的概念便可应用到C语言的所有应用场合(只人资源充足)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。