如摘要所說,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++編譯出來的可執行文件遠多於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++干得漂亮。
https://yq.aliyun.com/articles/33326?spm=5176.100239.blogrightarea33100.17.9NBJrk