C語言面向對象編程(四):面向接口編程

 Java 中有 interface 關鍵字,C++ 中有抽象類或純虛類能夠與 interface 比擬,C 語言中也能夠實現相似的特性。編程

    接口和抽象類有什麼區別?數據結構

    不少編程書籍也常常說要面向接口編程,個人理解是,接口強制派生類必須實現基類(接口)定義的契約,而抽象類則容許實現繼承從而致使派生類能夠不實現(重寫)基類(接口)定義的契約。一般這不是問題,但在有一些特定的狀況,看起來不那麼合適。函數

    好比定義一個 Shape 基類,其中定義一個 draw() 方法,給一個什麼都不作的默認實現(一般是空函數體),這實際沒有任何意義。spa

    再好比基類改變某個方法的實現,而派生類採用實現繼承並無重寫這個方法,此時可能會致使一些奇怪的問題。以鳥爲例,基類爲 Bird ,咱們可能會定義一個 fly() 方法,一個 walk() 方法,由於有的人認爲鳥既能夠走又能夠飛。開始時咱們在 walk() 的實現裏做了假定,認爲雙腳交叉前進纔是 walk ,但是後來發現有些鳥是雙腳一齊蹦的,不會交叉前進。這個時候怎麼辦?基類 Bird 的 walk() 方法是否要修改、如何修改?.net

    在 C++ 中,沒有接口關鍵字 interface ,同時爲了代碼複用,常常採用實現繼承。在 C 語言中,咱們前面幾篇文章討論了封裝、隱藏、繼承、虛函數、多態等概念,雖然均可以實現,但使用起來總不如自帶這些特性的語言(如 C++ 、Java )等駕輕就熟。一旦你採用咱們前面描述的方法來進行面向對象編程,就會發現,在 C 語言中正確的維護類層次是一件很是繁瑣、容易出錯的事情,並且要比面向對象的語言多寫不少代碼(這很容易理解,面嚮對象語言自帶輪子,而 C 要本身造輪子,每實現一個類都要造一遍)。但有一點,當咱們使用 C 語言做面向對象編程時,比 C++ 有明顯的優點,那就是接口。指針

    接口強制派生類實現,這點在 C 中很容易作到。並且咱們在編程中,實際上多數時候也不須要那麼多的繼承層次,一個接口類做爲基類,一個實現類繼承接口類,這基本就夠了。在 C 語言中採用這種方式,能夠不考慮析構函數、超過 3 層繼承的上下類型轉換、虛函數調用回溯、虛函數表裝配等等問題,咱們所要作的,就是實現基類接口,經過基類指針,就只能操做繼承層次中最底層的那個類的對象;而基類接口,天生就是不能實例化的(實際上是實例化了沒辦法使用,由於結構體的函數指針沒人給它賦值)。orm

    一個示例以下:server

[cpp] view plain copy對象

  1. struct base_interface {  blog

  2.     void (*func1)(struct base_interface* b);  

  3.     void (*func2)(struct base_interface* b);  

  4.     int (*func_3)(struct base_interface* b, char * arg);  

  5. };  

  6.   

  7. struct derived {  

  8.     struct base_interface bi;  

  9.     int x;  

  10.     char ch;  

  11.     char *name;  

  12. };  


    上面是頭文件,derived 結構體經過包含 base_interface 類型的成員 bi 來達到繼承的效果;而 base_interface 沒法實例化,咱們沒有提供相應的構造函數,也沒有提供與 func_1 , func_2 等函數指針對應的實現,即使有人 malloc 了一個 base_interface ,也沒法使用。

    derived 類能夠提供一個構造函數 new_derived ,同時在實現文件中提供 func_1 , func_2 ,func_3 的實現並將函數地址賦值給 bi 的成員,從而完成 derived 類的裝配,實現 base_interface 定義的契約。

    示例實現以下:

[cpp] view plain copy

  1. static void _derived_func_1(struct base_interface *bi)  

  2. {  

  3.     struct derived * d = (struct derived*)bi;  

  4.     d->x *= 2;  

  5.     printf("d->name = %s\n", d->name);  

  6. }  

  7.   

  8. /* _derived_func_2 impl */  

  9. /* _derived_func_3 impl */  

  10.   

  11. struct derived *new_derived()  

  12. {  

  13.     struct derived *d = malloc(sizeof(struct derived));  

  14.     d->bi.func_1 = _derived_func_1;  

  15.     d->bi.func_2 = _derived_func_2;  

  16.     d->bi.func_3 = _derived_func_3;  

  17.     d->x = 0;  

  18.     d->ch = 'a';  

  19.     d->name = NULL;  

  20.   

  21.     return d;  

  22. }  


    咱們能夠這麼使用 base_interface 接口:

[cpp] view plain copy

  1. void do_something(struct base_interface *bi)  

  2. {  

  3.     bi->func_1(bi);  

  4. }  

  5.   

  6. int main(int argc, char **argv)  

  7. {  

  8.     struct derived * d = new_derived();  

  9.     do_something((struct base_interface*)d);  

  10.   

  11.     return 0;  

  12. }  


    上面的代碼中 do_something 函數徹底按照接口編程,而 bi 能夠實際指向任意一個實現了 base_interface 接口的類的實例,在必定程序上達到多態的效果,花費的代價至關小,卻可讓咱們的程序提升可擴展性,下降耦合。

    這種簡單的方法也是我在本身的項目中使用的方法,效果不錯。

    好啦,C 語言面向對象編程系列的基礎性介紹就告一段落,下面是前幾篇的連接,有興趣的能夠回頭看看:

    接下來我會提供幾個實做的例子,包括基本的數據結構,如單鏈表、樹,還有一個 http server 的例子。


網友評論:

既然用到了malloc那應該也寫個free吧?

謝謝提醒,實際使用時,free在別處。

相關文章
相關標籤/搜索