在 C 語言面向對象編程(一)裏說到繼承,這裏再詳細說一下。java
C++ 中的繼承,從派生類與基類的關係來看(出於對比 C 與 C++,只說公有繼承):編程
派生類內部能夠直接使用基類的 public 、protected 成員(包括變量和函數)框架
使用派生類的對象,能夠像訪問派生類本身的成員同樣訪問基類的成員函數
對於被派生類覆蓋的基類的非虛函數,在派生類中能夠經過基類名和域做用符(::)來訪問this
當使用基類指針調用虛函數時,會調用指針指向的實際對象實現的函數,若是該對象未重載該虛函數,則沿繼承層次,逐級回溯,直到找到一個實現spa
上面的幾個特色,咱們在 C 語言中可否所有實現呢?我以爲能夠實現相似的特性,但在使用方法上會有些區別。後面咱們一個一個來講,在此以前呢,先說繼承的基本實現。.net
先看 C 語言中經過「包含」模擬實現繼承的簡單代碼框架:指針
[cpp] view plain copyorm
struct base{ 對象
int a;
};
struct derived{
struct base parent;
int b;
};
struct derived_2{
struct derived parent;
int b;
};
上面的示例只有數據成員,函數成員實際上是個指針,能夠看做數據成員。 C 中的 struct 沒有訪問控制,默認都是公有訪問(與 java 不一樣)。
下面是帶成員函數的結構體:
[cpp] view plain copy
struct base {
int a;
void (*func1)(struct base *_this);
};
struct derived {
struct base parent;
int b;
void (*func2)(struct derived* _this;
};
爲了像 C++ 中同樣經過類實例來訪問成員函數,必須將結構體內的函數指針的第一個參數定義爲自身的指針,在調用時傳入函數指針所屬的結構體實例。這是由於 C 語言中不存在像 C++ 中那樣的 this 指針,若是咱們不顯式地經過參數提供,那麼在函數內部就沒法訪問結構體實例的其它成員。
下面是在 c 文件中實現的函數:
[cpp] view plain copy
static void base_func1(struct base *_this)
{
printf("this is base::func1\n");
}
static void derived_func2(struct derived *_this)
{
printf("this is derived::func2\n");
}
C++ 的 new 操做符會調用構造函數,對類實例進行初始化。 C 語言中只有 malloc 函數族來分配內存塊,咱們沒有機會來自動初始化結構體的成員,只能本身增長一個函數。以下面這樣(略去頭文件中的聲明語句):
[cpp] view plain copy
struct base * new_base()
{
struct base * b = malloc(sizeof(struct base));
b->a = 0;
b->func1 = base_func1;
return b;
}
好的,構造函數有了。經過 new_base() 調用返回的結構體指針,已經能夠像類實例同樣使用了:
[cpp] view plain copy
struct base * b1 = new_base();
b1->func1(b1);
到這裏咱們已經知道如何在 C 語言中實現一個基本的「類」了。接下來一一來看前面提到的幾點。
第一點,派生類內部能夠直接使用基類的 public 、protected 成員(包括變量和函數)。具體到上面的例子,咱們能夠在 derived_func2 中訪問基類 base 的成員 a 和 func1 ,沒有任何問題,只不過是顯式經過 derived 的第一個成員 parent 來訪問:
[cpp] view plain copy
static void derived_func2(struct derived *_this)
{
printf("this is derived::func2, base::a = %d\n", _this->parent.a);
_this->parent.func1(&_this->parent);
}
第二點,使用派生類的對象,能夠像訪問派生類本身的成員同樣訪問基類的成員。這個有點變化,仍是隻能經過派生類實例的第一個成員 parent 來訪問基類的成員(經過指針強制轉換的話能夠直接訪問)。代碼以下:
[cpp] view plain copy
struct derived d;
printf("base::a = %d\n",d.parent.a);
struct derived *p = new_derived();
((struct base *)p)->func1(p);
第三點,對於被派生類覆蓋的基類的非虛函數,在派生類中能夠經過基類名和域做用符(::)來訪問。其實經過前兩點,咱們已經熟悉了在 C 中訪問「基類」成員的方法,老是要經過「派生類」包含的放在結構體第一個位置的基類類型的成員變量來訪問。因此在 C 中,嚴格來說,實際上不存在覆蓋這種狀況。即使定義了徹底同樣的函數指針,也沒有關係,由於「包含」這種方式,已經從根本上分割了「基類」和「派生類」的成員,它們不在一個街區,不會衝突。
下面是一個所謂覆蓋的例子:
[cpp] view plain copy
struct base{
int a;
int (*func)(struct base * b);
};
struct derived {
struct base b;
int (*func)(struct derived *d);
};
/* usage */
struct derived * d = new_derived();
d->func(d);
d->b.func((struct base*)d);
如上面的代碼所示,不存在名字覆蓋問題。
第四點,虛函數。虛函數是 C++ 裏面最有意義的一個特性,是多態的基礎,要想講明白比較困難,咱們接下來專門寫一篇文章講述如何在 C 中實現相似虛函數的效果,實現多態。
網友評論:
new_derived();的實現沒給出來啊,是否是這樣啊?
[cpp] view plain copy
struct derived * new_derived()
{
struct derived * d = malloc(sizeof(struct derived));
d->b = 0;
d->func2 = derived_func2;
d->parent = new_base();
return d;
}