NO IMAGE

    在 C 語言物件導向程式設計(一)裡說到繼承,這裡再詳細說一下。

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

  • 派生類內部可以直接使用基類的 public 、protected 成員(包括變數和函式)
  • 使用派生類的物件,可以像訪問派生類自己的成員一樣訪問基類的成員
  •  對於被派生類覆蓋的基類的非虛擬函式,在派生類中可以通過基類名和域作用符(::)來訪問
  • 當使用基類指標呼叫虛擬函式時,會呼叫指標指向的實際物件實現的函式,如果該物件未過載該虛擬函式,則沿繼承層次,逐級回溯,直到找到一個實現

    上面的幾個特點,我們在 C 語言中能否全部實現呢?我覺得可以實現類似的特性,但在使用方法上會有些區別。後面我們一個一個來說,在此之前呢,先說繼承的基本實現。

    先看 C 語言中通過“包含”模擬實現繼承的簡單程式碼框架:

struct base{
int a;
};
struct derived{
struct base parent;
int b;
};
struct derived_2{
struct derived parent;
int b;
};

    上面的示例只有資料成員,函式成員其實是個指標,可以看作資料成員。 C 中的 struct 沒有訪問控制,預設都是公有訪問(與 java 不同)。

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

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 檔案中實現的函式:

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 函式族來分配記憶體塊,我們沒有機會來自動初始化結構體的成員,只能自己增加一個函式。如下面這樣(略去標頭檔案中的宣告語句):

struct base * new_base()
{
struct base * b = malloc(sizeof(struct base));
b->a = 0;
b->func1 = base_func1;
return b;
}

    好的,建構函式有了。通過 new_base() 呼叫返回的結構體指標,已經可以像類例項一樣使用了:

struct base * b1 = new_base();
b1->func1(b1);

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

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

 

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 來訪問基類的成員(通過指標強制轉換的話可以直接訪問)。程式碼如下:

struct derived d;
printf("base::a = %d\n",d.parent.a);
struct derived *p = new_derived();
((struct base *)p)->func1(p);

    第三點,對於被派生類覆蓋的基類的非虛擬函式,在派生類中可以通過基類名和域作用符(::)來訪問。其實通過前兩點,我們已經熟悉了在 C 中訪問“基類”成員的方法,總是要通過“派生類”包含的放在結構體第一個位置的基類型別的成員變數來訪問。所以在 C 中,嚴格來講,實際上不存在覆蓋這種情況。即便定義了完全一樣的函式指標,也沒有關係,因為“包含”這種方式,已經從根本上分割了“基類”和“派生類”的成員,它們不在一個街區,不會衝突。

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

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 中實現類似虛擬函式的效果,實現多型。

    回顧一下: