C語言面向對象編程(二):繼承詳解

在 C 語言面向對象編程(一)裏說到繼承,這裏再詳細說一下。java

    C++ 中的繼承,從派生類與基類的關係來看(出於對比 C 與 C++,只說公有繼承):編程

  • 派生類內部能夠直接使用基類的 public 、protected 成員(包括變量和函數)框架

  • 使用派生類的對象,能夠像訪問派生類本身的成員同樣訪問基類的成員函數

  •  對於被派生類覆蓋的基類的非虛函數,在派生類中能夠經過基類名和域做用符(::)來訪問this

  • 當使用基類指針調用虛函數時,會調用指針指向的實際對象實現的函數,若是該對象未重載該虛函數,則沿繼承層次,逐級回溯,直到找到一個實現spa

    上面的幾個特色,咱們在 C 語言中可否所有實現呢?我以爲能夠實現相似的特性,但在使用方法上會有些區別。後面咱們一個一個來講,在此以前呢,先說繼承的基本實現。.net

    先看 C 語言中經過「包含」模擬實現繼承的簡單代碼框架:指針

[cpp] view plain copyorm

  1. struct base{  對象

  2.     int a;  

  3. };  

  4.   

  5. struct derived{  

  6.     struct base parent;  

  7.     int b;  

  8. };  

  9.   

  10. struct derived_2{  

  11.     struct derived parent;  

  12.     int b;  

  13. };  


    上面的示例只有數據成員,函數成員實際上是個指針,能夠看做數據成員。 C 中的 struct 沒有訪問控制,默認都是公有訪問(與 java 不一樣)。

    下面是帶成員函數的結構體:

[cpp] view plain copy

  1. struct base {  

  2.     int a;  

  3.     void (*func1)(struct base *_this);  

  4. };  

  5.   

  6. struct derived {  

  7.     struct base parent;  

  8.     int b;  

  9.     void (*func2)(struct derived* _this;  

  10. };  


    爲了像 C++ 中同樣經過類實例來訪問成員函數,必須將結構體內的函數指針的第一個參數定義爲自身的指針,在調用時傳入函數指針所屬的結構體實例。這是由於 C 語言中不存在像 C++ 中那樣的 this 指針,若是咱們不顯式地經過參數提供,那麼在函數內部就沒法訪問結構體實例的其它成員。

    下面是在 c 文件中實現的函數:

[cpp] view plain copy

  1. static void base_func1(struct base *_this)  

  2. {  

  3.     printf("this is base::func1\n");  

  4. }  

  5. static void derived_func2(struct derived *_this)  

  6. {  

  7.     printf("this is derived::func2\n");  

  8. }  


    C++ 的 new 操做符會調用構造函數,對類實例進行初始化。 C 語言中只有 malloc 函數族來分配內存塊,咱們沒有機會來自動初始化結構體的成員,只能本身增長一個函數。以下面這樣(略去頭文件中的聲明語句):

[cpp] view plain copy

  1. struct base * new_base()  

  2. {  

  3.     struct base * b = malloc(sizeof(struct base));  

  4.     b->a = 0;  

  5.     b->func1 = base_func1;  

  6.     return b;  

  7. }  


    好的,構造函數有了。經過 new_base() 調用返回的結構體指針,已經能夠像類實例同樣使用了:

[cpp] view plain copy

  1. struct base * b1 = new_base();  

  2. b1->func1(b1);  


    到這裏咱們已經知道如何在 C 語言中實現一個基本的「類」了。接下來一一來看前面提到的幾點。

   第一點,派生類內部能夠直接使用基類的 public 、protected 成員(包括變量和函數)。具體到上面的例子,咱們能夠在 derived_func2 中訪問基類 base 的成員 a 和 func1 ,沒有任何問題,只不過是顯式經過 derived 的第一個成員 parent 來訪問:

 

[cpp] view plain copy

  1. static void derived_func2(struct derived *_this)  

  2. {  

  3.     printf("this is derived::func2, base::a = %d\n", _this->parent.a);  

  4.     _this->parent.func1(&_this->parent);  

  5. }  


    第二點,使用派生類的對象,能夠像訪問派生類本身的成員同樣訪問基類的成員。這個有點變化,仍是隻能經過派生類實例的第一個成員 parent 來訪問基類的成員(經過指針強制轉換的話能夠直接訪問)。代碼以下:

[cpp] view plain copy

  1. struct derived d;  

  2. printf("base::a = %d\n",d.parent.a);  

  3.   

  4. struct derived *p = new_derived();  

  5. ((struct base *)p)->func1(p);  


    第三點,對於被派生類覆蓋的基類的非虛函數,在派生類中能夠經過基類名和域做用符(::)來訪問。其實經過前兩點,咱們已經熟悉了在 C 中訪問「基類」成員的方法,老是要經過「派生類」包含的放在結構體第一個位置的基類類型的成員變量來訪問。因此在 C 中,嚴格來說,實際上不存在覆蓋這種狀況。即使定義了徹底同樣的函數指針,也沒有關係,由於「包含」這種方式,已經從根本上分割了「基類」和「派生類」的成員,它們不在一個街區,不會衝突。

    下面是一個所謂覆蓋的例子:

[cpp] view plain copy

  1. struct base{  

  2.     int a;  

  3.     int (*func)(struct base * b);  

  4. };  

  5.   

  6. struct derived {  

  7.     struct base b;  

  8.     int (*func)(struct derived *d);  

  9. };  

  10.   

  11. /* usage */  

  12. struct derived * d = new_derived();  

  13. d->func(d);  

  14. d->b.func((struct base*)d);  

    如上面的代碼所示,不存在名字覆蓋問題。


    第四點,虛函數。虛函數是 C++ 裏面最有意義的一個特性,是多態的基礎,要想講明白比較困難,咱們接下來專門寫一篇文章講述如何在 C 中實現相似虛函數的效果,實現多態。

網友評論:

new_derived();的實現沒給出來啊,是否是這樣啊?

[cpp] view plain copy

  1. struct derived * new_derived()    

  2. {    

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

  4.     d->b = 0;  

  5.     d->func2 = derived_func2;    

  6.     d->parent = new_base();  

  7.     return d;    

  8. }   

相關文章
相關標籤/搜索