物件導向程式設計的核心思想是資料抽象、繼承和動態繫結。
通過繼承聯絡在一起的類構成一種層次關係,通常在層次關係的根部有乙個基類,其他類則直接或間接的從基類繼承而來,這些繼承得到的類稱為派生類。基類負責定義在層次關係中所有類共同擁有的成員,而每個派生類定義各自特有的成員。
派生類必須通過使用類派生列表明確指出從哪個或者哪些基類繼承而來,類派生列表的形式是:冒號,後面緊跟以逗號分隔的基類列表,其中每個基類前面都可以有訪問說明符。
如果基類希望他的派生類各自定義適合自身版本的函式,此時基類就將這些函式宣告成虛函式。c++ 11 新標準允許派生類顯示地註明它將使用哪個成員函式改寫基類的虛函式,具體措施是在函式的形參列表之後再加乙個override
關鍵字。
在 c++ 語言中,使用基類的引用或者指標呼叫乙個虛函式時將發生動態繫結。
class quote
std::string isbn() const
virtual double net_price(std::size_t n) const
virtual ~quote() = default; //對析構函式進行動態繫結
private:
std::string bookno; //書籍的 isbn 編號
protected:
double price = 0.0; //表示普通狀態下不打折的**
};
注意:
基類通常都應該定義乙個虛析構函式,即使該函式不執行任何實際的操作也是如此。
派生類可以繼承其基類的成員,但是針對特定的操作,派生類可以提供自己新的定義,以覆蓋從基類繼承而來的舊定義。
c++ 中必須將它的兩種成員函式區分開來:
對於第一種函式,基類通常將其定義為虛函式,當使用指標或者引用呼叫虛函式時,該呼叫將動態繫結,根據引用或指標所繫結的物件型別不同,該呼叫可能執行基類的版本,也可能執行某個派生類的版本。
基類通過在成員函式前面加上virtual
函式使得該函式執行動態繫結。
成員函式如果沒有被宣告為虛函式,則其解析過程發生在編譯時期而非執行時。
派生類可以繼承定義在基類中的成員,但是派生類的成員函式不一定有權訪問從基類繼承而來的成員。派生類可以訪問公有成員,而不能訪問私有成員,protected
成員是基類希望它的派生類能夠訪問該成員,但是同時禁止其他使用者訪問。
派生類需要使用類派生列表明確指出它是從哪個或哪些基類繼承而來的,類派生列表的形式是:冒號後面緊跟逗號分隔的基類列表,每個基類前面都要指明訪問說明符:public
,protected
,private
。
class bulk_quote : public quote
;
繼承自乙個類的形式稱為單繼承。
派生類經常但不總是覆蓋它繼承的虛函式,如果派生類沒有覆蓋其基類中的某個虛函式,則該虛函式的行為類似於其他的普通成員,派生類會直接繼承其在基類中的版本。
乙個派生類物件包含多個組成部分:含有派生類自定義的物件,繼承自基類的物件。
c++ 中沒有明確規定派生類的物件在記憶體中如何分布,但是可以 認為bulk_quote
物件包含如下兩個部分:
因為在派生類物件中含有與其基類對應的組成部分,所以能把派生類的物件當成基類物件使用,而且能將基類的指標或引用繫結到派生類物件中的基類部分:
quote item; // 基類物件
bulk_quote bulk; // 派生類物件
quote *p = &item; // p指向基類 quote 物件
p = &bulk; // p指向派生類 bulk_quote 物件的基類部分
quote &r = bulk; // r繫結到派生類 bulk_quote 物件的基類部分
上面介紹的轉換通常稱為 派生類到基類的型別轉換。這種型別轉換是編譯器隱式執行的。
儘管派生類包含了從基類繼承而來的成員,但是派生類不能直接初始化這些成員,而是需要使用基類的建構函式來初始化它們。也就是說:每個類控制它自己的成員初始化過程。
派生類物件的基類部分與派生類物件自己的資料成員都是在建構函式的初始化階段執行初始化操作的:
bulk_quote(const std::string& book,double p,std::size_t qty,double disc) : quote(book,p),min_qty(qty),discount(disc){}
除非指出,否則派生類物件的基類部分資料成員會執行預設初始化。
另外,首先會初始化基類的部分,然後按照宣告的順序依次初始化派生類的成員。
派生類可以訪問其基類中的公有成員和受保護的成員。
如果基類定義了乙個靜態成員,則在整個繼承體系中只存在該成員的唯一定義,無論從基類中派生出來多少個派生類,對於每個靜態成員來說都只存在唯一的例項。
靜態成員遵循通用的訪問控制規則。如果某個靜態成員是可以訪問的,那麼既可以通過基類也可以通過派生類使用它。
派生類的宣告包含類名但是不包含它的派生列表:
class bulk_quote : public quote; // 錯誤,派生類列表不能出現在這裡
class bulk_quote; // 正確,宣告派生類的正確方式
如果想使用某個類作為基類,則該類必須是已經定義而非僅僅宣告:
class quote; // 宣告,但未定義
class bulk_quote : public quote ; // 錯誤,quote必須先被定義
派生類中包含並且可以使用它從基類繼承而來的成員,為了使用這些成員,派生類當然要先知道它們是什麼,因此規定還有一層隱含的意思,即乙個類不能派生它本身。
乙個類是基類,同時也可以是派生類:
class base;
calss d1 : public base ;
class d2 : public d1 ;
base
是d1
的直接基類,同時是d2
的間接基類。直接基類出現在派生列表中,而間接基類由派生類通過其直接基類繼承而來。
如果想定義乙個類並且不希望從它派生出新的類,可以禁止繼承的方式,c++ 11 新標準中在類名後面緊跟乙個關鍵字final
即可實現:
class noderived final; // noderived 不能作為基類
通常情況下引用或指標繫結到乙個物件時,引用或指標的型別應與物件的型別一致,或者物件的型別含有乙個可接受的const
型別轉換規則。存在繼承關係的類是乙個重要的例外:可以將基類的指標或引用繫結到派生類的物件上。
可以將基類的指標或引用繫結到派生類物件上是一層極為重要的含義:當使用基類的引用或指標時,實際上我們並不清楚該引用或指標所繫結物件的真實型別。該物件可能是基類的物件,也可能是派生類的物件。
智慧型指標也支援派生類向基類的型別轉換。
當使用存在繼承關係的型別時,必須將乙個變數或其他表示式的靜態型別與該表示式表示物件的動態型別區分開來,表示式的靜態型別在編譯時總是已知的,它是變數宣告時的型別或表示式生成的型別。動態型別則是變數或表示式表示的記憶體中的物件型別,動態型別直到執行時才可以知道。
如果表示式既不是引用也不是指標,則它的動態型別永遠與靜態一致。
之所以存在派生類向基類的型別轉換是因為每個派生類物件都包含乙個基類部分,基類的引用或指標可以繫結到這部分上。
因為乙個基類的物件可能是派生類物件的一部分,也可能不是,所以不存在從基類向派生類的自動型別轉換:
quote base;
bulk_quote bulkp = &base; // 錯誤,不能將基類轉換成派生類
bulk_quote& bulkref = base; // 錯誤,不能將基類轉換成派生類
如果上述轉換合法,則我們有可能會使用bulkp
或bulkref
來訪問base
中根本不存在的資料成員。
除此之外還有一種情況顯得有點特別,即使乙個基類指標或引用繫結在乙個派生類物件上,也不能執行從基類向派生類的轉換:
bulk_quote bulk;
quote *itemp = &bulk; //正確,動態型別是 bulk_quote
bulk_quote *bulkp = itemp; //錯誤,不能將基類轉換成派生類
如果在基類中含有乙個或多個虛函式,可以使用dynamic_cast
來請求乙個型別轉換,該轉換的安全檢查將在執行時執行,同樣。如果已知某個基類向派生類轉換是安全的,則可以使用static_cast
來強制覆蓋掉編譯器的檢查工作。
派生類向基類的自動型別轉換只針對指標或引用型別有效,在派生類型別和基類型別之間不存在裝的。
定義基類和派生類
作為繼承關係中根節點的類通常都會定義乙個虛析構函式。基類通常都應該定義乙個虛析構函式,即使該函式不執行任何實際操作也是如此。成員函式與繼承 在c 語言中,基類必須將它的兩種成員函式區分開來 一種是基類希望其派生類進行覆蓋的函式 另一種是基類希望派生類直接繼承而不要改變的函式。對於前者,基類通常將其定...
定義基類和派生類
定義基類 對於基類,我們需要記住的是作為繼承關係中根節點的類通常都會定義乙個虛析構函式。基類通常都會定義乙個虛析構函式,即使該函式不執行任何實際操作也是如此。成員函式和繼承 派生類可以繼承其基類的成員,也可以對基類中的虛函式進行重新定義。換句話說,派生類需要對這些操作提供自己的新定義以覆蓋 over...
15 2 定義基類和派生類
目錄15.2.2 定義派生類 15.2.3 型別轉換與繼承 定義quote類 class quote string isbn const 返回給定數量的書籍的銷售總額 派生類負責編寫並使用不同的折扣演算法 virtual double net price int n const virtual qu...