如摘要所说,C语言不支持OOP(面向对象的编程)。并这不意味着我们就不能对C进行面向对象的开发,只是过程要复杂许多。原来以C++的许多工作,在C语言中需我们手动去完成。
博主将与大家一起研究一下如下用C语言实现面象对象的编程。
面向对象的三大特性:封装、继承、多态
我们要达到的目的如下:
Animal是动物,有两个方法:Eat()吃,Breed()繁衍。
Bird与Mammal都是Animal,Mammal是哺乳动物。
Penguin是企鹅,企鹅是Bird,企鹅不会飞。
Swallow是燕子,是Bird,会飞。
Bat是蝙蝠,是Mammal,会飞。
Tiger是老虎,是Mammal,不会飞。
Plane是飞机,会飞。它不是动物。
从上面的类继承关系来看,由于Swallow, Bat, Plane会飞,所以它们都继承了IFly接口。
首先我们用C++的类来实现上面的关系:
class Animal { public: virtual void Eat() = 0; virtual void Breed() = 0; }; class Bird : public Animal { public: virtual void Breed() { cout << "蛋生" << endl; } }; class Mammal : public Animal { public: virtual void Breed() { cout << "胎生" << endl; } }; class IFly { public: virtual void Fly() = 0; }; class Penguin : public Bird { public: virtual void Eat() { cout << "企鹅吃鱼" << endl; } }; class Swallow : public Bird , public IFly { public: virtual void Eat() { cout << "燕子吃虫子" << endl; } virtual void Fly() { cout << "燕子飞呀飞" << endl; } }; class Bat : public Mammal, public IFly { public: virtual void Eat() { cout << "蝙蝠吃飞虫" << endl; } virtual void Fly() { cout << "蝙蝠飞呀飞" << endl; } }; class Tiger : public Mammal { virtual void Eat() { cout << "老虎吃肉" << endl; } }; class Plane : public IFly { public: virtual void Fly() { cout << "飞机飞过天空" << endl; } };
用下面的main.cpp来测试它们的继承效果:
int main() { Penguin *penguin = new Penguin; Swallow *swallow = new Swallow; Bat *bat = new Bat; Tiger *tiger = new Tiger; Plane *plane = new Plane; Animal* animals[4] = {penguin, swallow, bat, tiger}; IFly* flies[3] = {swallow, bat, plane}; for (int i = 0; i < 4; ++i) { animals[i]->Eat(); animals[i]->Breed(); } cout << "-------------" << endl; for (int i = 0; i < 3; ++i) flies[i]->Fly(); delete penguin; delete swallow; delete bat; delete tiger; delete plane; return 0; }
执行的效果是:
企鹅吃鱼 蛋生 燕子吃虫子 蛋生 蝙蝠吃飞虫 胎生 老虎吃肉 胎生 ------------- 燕子飞呀飞 蝙蝠飞呀飞 飞机飞过天空
上面演示的就是C++的多态功能。
多态这个特性给我们软件灵活性带来了很大的便利。由于某此限制,如硬件资源不够充裕、开发环境不支持C++等原理,我们不能使用C++。
那么我们下面要讨论的是用C来重新实现上面的多态功能。
main.c大致是这样子的:
int main() { Object* penguin = Penguin_New(); Object* swallow = Swallow_New(); Object* bat = Bat_New(); Object* tiger = Tiger_New(); Object* plane = Plane_New(); Object* animal[4] = {penguin, swallow, bat, tiger}; IFly* flies[3] = {NULL}; flies[0] = Swallow_AsIFly(swallow); flies[1] = Bat_AsIFly(bat); flies[2] = Plane_AsIFly(plane); for (int i = 0; i < 4; ++i) { Animal_Eat(animal[i]); Animal_Breed(animal[i]); } for (int i = 0; i < 4; ++i) { IFly_Fly(flies[i]); } Penguin_Delete(penguin); Swallow_Delete(swallow); Bat_Delete(bat); Tiger_Delete(tiger); Plane_Delete(plane); return 0; }
上面编译时需要加 "-std=c99" 才能通过编译。
博主已实现了上面的Demo,代码已提交到:http://git.oschina.net/hevake_lcj/C_OOP_DEMO
该Demo实现了OOP的类继承、多态的特性。继承只支持单继承,还没有实现接口功能。
每个对象由三部分组成:info, data, func
info,类信息,存储该对象的:类型ID、虚函数表地址
data,对象的数据
func,虚函数指针
如下为 info 的定义:
typedef struct { uint32_t tag; //! 高16位为TAG,低16位为class_id void* vfun; //! 虚函数结构体地址 } class_info_t;
例如 Animal 类的定义,见 animal_def.h :
typedef struct { int health; } Animal_Data; typedef struct { void (*Eat)(); void (*Breed)(); } Animal_Func; typedef struct { class_info_t info; Animal_Data data; Animal_Func func; } Animal;
结构图:
<明天再写>
即将讨论话题:
- 如何表述类的继承关系?
- 为什么要将data与func分开?
博主自己测试了一下,结果是:
$ ./c_oop_demo start 企鹅吃鱼 蛋生 燕子吃虫子 蛋生 蝙蝠吃飞虫 胎生 老虎要吃肉 胎生 end
从上看来,已达到了预期的多态效果。
居说C++编译出来的可执行文件远多于C。于是博主对比了一下c_oop_demo与C++编译的同功能的可执行文件cpp_demo。博主惊讶地发现 c_oop_demo 的文件大小既还比 cpp_demo大一点。况且上面的 c_oop_demo 还没有实现接口功能呢,要是实现了,那不更大?这不由令博主对用C实现OOP,以为可以节省空间的想法大为失望。
看来,在实现同样的oop功能下,C++编译出的输出文件比自己手把手写的c_oop_demo要小,说明C++在这方便做了不少的优化。相比之下,C++用50多行的代码实现的功能,用C(博主亲自统计的)居然要写近1000行代码。代码的可维护性远不及C++。说C++生成的文件庞大,真是冤枉了C++。用C完成同等功还不如C++干得漂亮。