LW_OOPC是Light-Weight Object-Oriendted Programming in(with) C的缩写, 总共一个h文件,包含20个宏,约130行代码,非常的轻量级,却很好地支持了很多面向对象的特性,比如继承、多态,可以优美地实现面向接口编程。这个框架系由台湾的高焕堂先生以及他的MISOO团队首创,之后由我继续改进优化。最后,经高焕堂同意以LGPL协议开源。
为什么要用面向对象
面向过程方式开发的系统,代码复杂、耦合性强、难以维护,随着我们所要解决的问题越来越复杂,代码页变得越来越复杂,越来越难以掌控,而面向对象改编了程序员的思维方式,以更加符合客观世界的方式来认识世界,通过合理的运用抽象、封装、继承和多态,更好的阻止程序,从而很好地应对这种复杂性。
为什么要在C语言中实现面向对象
C语言以其简洁明快、功能强大的特点,深得开发人员的喜爱,尤其是在嵌入式开发领域,C语言更是占据了老大的地位。在我看来,语言只是工具,作为程序员,我们要做的是:选择合适的语言,解决恰当的问题。我们要尊重事实,考虑开发环境(软硬件环境),考虑团队成员的水平,从商用工程的角度讲,选择团队成员擅长的语言进行开发,风险要小很多。
一些从JAVA/C#转到C的程序员,无法从面向对象切换到面向过程,但又必须与C语言同事们在遗留的C系统上开发软件,所以他们有时会非常困惑:C语言室面向过程的变成语言,如何实践面向对象,甚至面向接口编程呢?此时,就非常需要在C语言中实现面向对象的手段,而LW_OOPC正是应对这一难题的解决之道。
LW_OOPC宏介绍
简而言之,LW_OOPC是一套C语言的宏,都定义在1个.h文件中(lw_oopc.h,约130行代码)。如果需要内存泄露检测支持以及调试打印支持,那么还需要1个.c文件(lw_oopc.c,约145行代码)。LW_OOPC是一种C语言编程框架,用于支持在C语言中进行面向对象编程。
下面,先通过一个简单的示例来展示LW_oopc这套宏的使用方法。我们要创建这样一些对象:动物(Animal),鱼(Fish),狗(Dog),车子(Car)。显然,鱼和狗都属于动物,都会动。车子也会动,但是车子不是动物。会动是这些对象的共同特征,但是,显然他们不属于一个家族。因此,我们首先考虑抽象出一个接口(IMoveable),以描述会动这一行为特征。
INTERFACE(IMoveable)
{
void (*move)(IMoveable* t);
};
INTERFACE宏用于定义接口,其成员(方法)均是函数指针类型。
然后,我们分析Animal,它应该是抽象类还是接口呢?动物都会吃,都需要呼吸,如果仅仅考虑这两个特征,显然可以把Animal定义接口。这里,为了展示抽象类在LW_OOPC中如何应用。我们让Animal拥有昵称和年龄属性,并且让动物和我们打招呼(sayHello方法),但是我们不允许用户直接创建Animal对象,这里,我们把Animal定位抽象类。
ABS_CLASS(Animal)
{
char name[128];//动物的昵称(假设小于128个字符)
int age;//动物的年龄
void (*setName)(Animal* t,const char* name);//设置动物的昵称
void (*setAge)(Animal* t,const char* name);//设置动物的年龄
void (*setHello)(Animal* t);//动物打招呼
void (*eat)(Animal* t);//动物都会吃(抽象方法,由子类实现)
void (*breathe)(Animal* t)//动物都会呼吸(抽象方法,由子类实现)
void (*init)(Animal* t,const char* name,int age);
}
ABS_CLASS宏用于定义抽象类、紧接着,我们来定义Fish类(Dog类的定义与Fish类雷同),它继承动物,然后还实现了IMoveable接口:
CLASS(Fish)
{
EXTENDS(Animal);//继承Animal抽象类
IMPLEMENTS(IMoveable);//实现IMoveable接口
void (*init)(Fish* t,const char*name,int age);//初始化昵称和年龄
};
下面,我们来定义Car,车子不是动物,但可以Move,因此,让Car实现IMoveable接口即可:
CLASS(Car)
{
IMPLEMENTS(IMoveable);//实现IMoveable接口(车子不是动物,但可以Move)
};
接口、抽象类、具体类的定义都已经完成了。下面,我们开发实现它们。接口是不需要实现的,所以IMoveable没有对应的实现代码。Animal是抽象动物接口,是半成品,所以需要提供半成品的实现:
/*设置动物的昵称*/
void Animal_setName(Animal* t,const char* name)
{
strcpy(t—>name,name);
}
/*设置动物的昵称*/
void Animal_setAge(Animal* t,const char* name)
{
t—>age=age;
}
/*动物和我们打招呼*/
void Aniaml_sayHello(Animal* t)
{
printf("Hello!我是%s,今年%d岁了!/n",t—>name,t—>age);
}
/*初始化动物的昵称和年龄*/
void Animal_init(Animal* t,Const char* name,int age)
{
t—>setName(t,name);
t—>setAge(t,age);
}
ABS_CTOR(Animal)
FUNCTION_SETTING(setName,Animal_setName);
FUNCTION_SETTING(setAge,Animal_setAge);
FUNCTION_SETTING(sayHello,Animal_sayHello);
FUNCTION_SETTING(init,Animal_init);
END_ABS_CTOR
ABS_CTOR表示抽象类的构造函数定义开始。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了给对象分配内存,紧接着要为结构体中的函数指针成员赋值,这一过程可以成为函数绑定,由FUNCTION_SETTING宏来完成。
下面看Fish类的实现(Dog类的实现与Fish类的实现雷同,篇幅所限,代码略省):
/*鱼的吃行为*/
void Fish_eat(Animal* t)
{
printf("鱼吃水草!/n");
}
/*鱼的呼吸行为*/
void Fish_breathe(Animal* t)
{
printf("鱼用腮呼吸!/n");
}
/*鱼的移动行为*/
void Fish_move(IMoveable* t)
{
printf("鱼在水里游!/n")
}
/*初始化鱼的昵称和年龄*/
void Finsh_init(Fish* t,const char* name,int age)
{
Animal* animal = SUPER_PTR(t,Animal);
animal—>setAge(animal,age);
}
CTOR(Fish)
SUPER_CTOR(Animal);
FUNCTION_SETTING(Animal.eat,Fish_eat);
FUNCTION_SETTING(Animal.breathe,Fish_breathe);
FUNCTION_SETTING(IMoveable.move,Fish_move);
FUNCTION_SETTING(init,Fish_init);
END_CTOR
上面的代码片段中,SUPER_PTR表示“向上转型”,将对象指针向上转型成直接父类或者直接接口类型的指针,SUPER_CTOR宏表示调用其直接父类的构造函数(类似Java语言中的super调用,在这里,其实质是要先调用父类的函数绑定过程,再调用自身的函数绑定过程)。
Car类的实现:
void Car_move(IMoveable* t)
{
printf("汽车在开动!/n");
}
CTOR(Car)
FUNCTION_SETTING(IMoveable.move,Car_move);
END_CTOR
下面,我们来实现main方法:
#include "animal.h"
int main()
{
//创建鱼对象
Fish* fish=Fish_new();
//创建狗对象
Dog* dog = Dog_new();
//创建车子对象
Car* car= Car_new();
Animal* animals[2] = {0};//初始化动物容器
IMoveable* moveObjs[3] = {0};//初始化可移动物体容器
int i=0;
int j=0;
//对象初始化
fish—>init(fish,"小鲤鱼",1);
dog—>init(dog,"牧羊犬",2);
//将fish和dog放入父类型的容器
animals[0]=SUPER_PTR(fish,Animal);
animals[1]=SUPER_PTR(dog,Animal);
//将fish和dog放入接口类类型的容器
moveObjs[0]=SUPER_PTR(fish,IMoveable);
moveObjs[1]=SUPER_PTR(dog,IMoveable);
moveObjs[2]=SUPER_PTR(car,IMoveable);
//打印所有动物的信息
for(i=0li<2;i++)
{
Animal* animal = animals[i];
animal—>eat(animal);
animal—>breathe(animal);
animal—>sayHello(animal);
}
}
//打印所有可移动物体的信息
for(j=0;j<3;j++)
{
IMoveable* moveObj = moveObjs[j];
moveObj—>move(moveObj);
}
lw_oopc_delete(fish);
lw_oopc_delete(dog);
lw_oopc_delete(car);
return 0;
}
从上边的代码中,我们惊喜地发现,在C语言中,借助LW_OOPC,我们实现了将不同的动物(Fish和Dog对象)装入Animal容器,然后可以用完全相同的方式调用Animal的方法(比如eat和breathe方法),而实际调用的是具体的实现类(Fish和Dog)的对应方法。这正是面向对象中的多态的概念。同样,我们可以将Fish、Dog、Car三个对象均视为可移动物体,均装入IMoveable容器,然后用完全相同的方式调用IMoveable接口的move方法。看到了吗?借助LW_OOPC,在C语言下可以轻松地实现面向对象和面向接口编程!