作為面向對象的程序設計來說,繼承是非常重要的一個特點,面向對象程序設計(Object-Oriented Programming, OOP)的核心思想是數據抽象、繼承和動態綁定。其中使用數據抽象,我們可以將類的接口與實現分離;使用繼承,可以定義相似的類型並對其相似關系建模;使用動態綁定,可以在一定程度上忽略相似類型的區別,而以統一的方式使用它們的對象。下面什么主要介紹的是繼承中基類和派生類之間的關系。
注:動態綁定只有當我們通過指針或者引用調用虛函數時才會發生。
在基類中有兩種類型的成員是我們需要注意的,一種是基類希望派生類繼承的;一種是基類希望派生類覆蓋的。如:基類Quote和其派生類Bulk_quote.
class Quote
{
public:
std::string isbn() const;
virtual double net_price(std::size_t n) const;
}
這里成員函數isbn()是基類希望派生類繼承的,派生類中不需要重新定義,而net_price是希望派生類覆蓋的,派生類中有不一樣的實現方式。這里基類Quote將希望派生覆蓋的成員函數定義成虛函數,即在前面加上:virtual關鍵字。
那么,那些成員函數需要定義成虛函數了,即基類和派生類中都有各自的實現方式的時候,就需要在基類中將該成員函數定義成虛函數,希望派生類將其覆蓋。動態綁定即是在我們使用基類的引用(或者指針)調用一個虛函數時發生的。這樣程序就可以根據參數的不同來判斷是調用基類的成員函數,還是調用派生類的成員函數。當然派生類也不是一定要重新定義基類中的虛函數,如果派生類沒有覆蓋基類中的虛函數,那么該虛函數就和其他普通函數一樣,派生類就直接繼承它在基類中的版本。
派生類和基類之間的類型轉換
上面例子中基類Quote里面有成員:bookNo和price,派生類Bulk_quote中有:min_qty和discount.當派生類繼承基類的時候,派生類中就有兩部分(四個)成員:一部分是繼承Quote的bookNo、price,一部分是自己定義的min_qty、discount。因為在派生類對象中含有與其基類對應的組成部分,所有我們能把派生類的對象當成基類對象來使用,而且我們也能將基類的指針或者引用綁定到派生類對象中的基類部分上。如下:
Quote item; //基類對象 Bulk_quote bulk; //派生類對象 Quote *p = &item; //p指向Quote對象 p = &bulk; //p指向bulk的Quote部分 Quote &r = bulk; //r綁定到bulk的Quote部分
注:在派生類對象中含有與其基類對應的組成部分,這一事實是繼承的關鍵所在;並且每個類控制它自己的成員初始化過程,即派生類不能直接初始化基類的成員,雖然這些成員是從基類繼承的,但是這些成員也必須使用基類的構造函數來初始化派生類的基類部分。
基類中的靜態成員
如果基類中定義了一個靜態成員,則在整個繼承體系中只存在該成員的唯一定義。不論從基類中派生出多少個類,對於每個靜態成員來說都只存在唯一的實例。對於靜態成員的訪問控制規則,則是和其他的成員一樣,如果靜態成員是private的,則派生類無權訪問。如果靜態成員是可以訪問的,則我們既能通過基類使用它們,也可以通過派生類使用它。
派生類的聲明
派生類的聲明與其他類差別不大,聲明中包含類名,但是不包含他的派生列表,如:
class Bulk_quote : public Quote; //錯誤:派生列表不能出現在這里 class Bulk_quote; //正確:聲明派生類的正確方式
注:派生類的列表以及與定義有關的其他細節必須與類的主題一起出現。
被用作基類的類
一個類可以被用來作為基類,那么首先該類必須已經定義了而非只是聲明,而且一個類可以是基類,也可以是派生類,如:
class Base {..............};
class D1 : public Base {..............};
class D2 : D1 {........................};
在上面的列子可以看到Base作為D1的基類,D1作為Base的派生類,而D1又作為D2的基類,D2作為D1的派生類。每個類都是繼承直接基類的所有成員,對於一個最終的派生類來說,它會繼承其直接基類的成員;該直接基類的成員又含有其基類的成員;依次類推直至繼承鏈的頂端。因此,最終的派生類將包含它的直接基類的子對象以及每個基類的子對象。
注:如果我們想防止一個類被繼承,那么可以在類名后跟一個關鍵final,如:
class NoDerived final {.............}
class Base {............}
class Last final : Base {...........} //Last不能作為基類
class Bad : NoDerived {..............} //錯誤:NoDerived不能做為基類
class Bad2 : Last {.................} //錯誤:Last不能作為基類
基類和派生類在對象之間不存在類型轉換
當我們用一個派生類對象為一個基類對象初始化或賦值時,只有該派生類對象中的基類部分會被拷貝、移動或賦值,它的派生類部分將被忽略掉。
如:
Bulk_quote bulk; //派生類對象 Quote item(bulk); //使用Quote::Quote(const Quote&)構造函數 item = bulk; //調用Quote::operator=(const Quote&)
當構造item時,運行Quote的拷貝函數。該函數只能處理bookNo和price兩個(這兩個是基類中的成員),它負責拷貝bulk中的Quote部分的成員,同事忽略掉bulk中Bulk_quote部分的成員。類似的,對於將bulk賦值給item的操作來說,只有bulk中Quote部分的成員被賦值給item。
