如摘要所說,C語言不支持OOP(面向對象的編程)。並這不意味着咱們就不能對C進行面向對象的開發,只是過程要複雜許多。原來以C++的許多工做,在C語言中需咱們手動去完成。git
博主將與你們一塊兒研究一下以下用C語言實現面象對象的編程。shell
面向對象的三大特性:封裝、繼承、多態編程
咱們要達到的目的以下:函數
Animal是動物,有兩個方法:Eat()吃,Breed()繁衍。oop
Bird與Mammal都是Animal,Mammal是哺乳動物。測試
Penguin是企鵝,企鵝是Bird,企鵝不會飛。優化
Swallow是燕子,是Bird,會飛。ui
Bat是蝙蝠,是Mammal,會飛。spa
Tiger是老虎,是Mammal,不會飛。.net
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++幹得漂亮。