C 還是 C++?
C++ 中的多態是指「通過基類對象的指針或者基類對象的引用調用虛函數」,表現更多派生類的特性,但根據 C++ 多態的實現,我們發現這種方法存在一定的空間和效率的折損。不可否認,多態輕松解決了很多工程中遇到的問題,這與 pure C 的解決方法比起來,更為優雅。
在考慮移植性上,C 的光芒要蓋過 C++,但 C++ 的多態是可借鑒的,於是用 pure C 來模仿 C++ 中多態行為。
C 如何實現多態
在 C 中沒有類的概念,但有 struct,而且 C 中的 struct 是不允許有函數的,只允許存在變量,那是不是函數變量就允許存在了?!所以,函數指針可以給我們一些提示。
假定有一個結構體 struct Animal 聲明如下:
struct Animal { void (*move)(); };
此時 Animal 中的 move 只是一個指針,並沒有賦值,也就是說它不能代表任何的行為,但我們可以在 main 函數中對其進行賦值,賦予相應的行為。
void Animal_move() { printf("Animal move.\n"); }
再假定一個結構體 struct Rabbit,我們可以暫且把它看作(因為 C 中沒有繼承概念)struct Animal 的派生類,聲明如下:
struct Rabbit { void (*move)(); };
同樣我們可以給 Rabbit 的 move 預定義一個行為:
void Rabbit_move() { printf("Rabbit move.\n"); }
struct Animal 和 struct Rabbit 在內容上完全一致,而且變量對齊上也完全一致,可以通過將 struct Rabbit 類型的指針強制轉換為 struct Animal 類型的指針,即:
Animal *panimal; Rabbit rabbit; //... panimal = (Animal*)&rabbit;
這樣,我們就可以通過 struct Animal 類型的指針或者引用來操縱 struct Rabbit 類型的對象了。
int main(void) { Animal *panimal; Rabbit rabbit; rabbit.move=Rabbit_move; panimal = (Animal*)&rabbit; panimal->move(); }
Rabbit move.
請按任意鍵繼續. . .
C 多態有什么問題
是不是有需要注意的問題?為什么剛才特別提出「在內容上完全一致,而且變量對齊上也完全一致」?倘若把 struct Animal 的聲明作稍微的改變:
struct Animal { int age; void (*move)(); };
運行崩潰了,並不能得到上面的執行結果,原因是 move 函數指針 在struct Animal 中和 struct Rabbit 中的偏移量不同,結構體是根據變量在結構體的偏移量來讀取或者修改變量的。當執行 panimal->move(); 的時候,實際上引用了非法的地址:
panimal->move();
可以被形象的轉化為:
( * (panimal+sizeof(age)) ) ();
但發現 panimal 是指向 struct Rabbit 實體的,panimal+sizeof(age) 已經指向了非法地址:
struct Rabbit 結構 | 調用 |
void (*move )(); | <------ rabbit.move(); |
非法地址 | <------ panimal->move(); |
因此需要模擬多態,必須保持函數指針變量對齊。
在一些 c 開源項目中經常用到這種設計,譬如 libevent。
搗亂 2013-05-09