面向對象程序設計的核心思想是:
數據抽象:實現類與接口的分離。
繼承:定義相似類型并進行相似關系建模。通過繼承聯系在一起的類構成一種層次關系。通常在在層次關系的根部有一個基類,其他類直接或間接由基類繼承而來。這些繼承的類稱為派生類。基類負責定義在層次關系中所有類共同擁有的成員,而每個派生類定義各自特有的成員。對于某些函數,基類希望它的派生類各自定義適合自身的版本,此時的基類就將這些函數聲明成虛函數。
class Quote{public: std::string isbn() const; virtual double net_PRice(std::size_t n) const;};派生類必須使用派生列表,必須將其繼承而來的成員函數中需要覆蓋的那些重新聲明:
class Bulk_Quote : public Quote//Bulj_Quote繼承了Quote{ double net_price(std::size_t n) const override;};動態綁定:一定程序忽略相似類型的區別,而以統一的方式使用他們的對象。 在C++語言中,當我們使用基類引用或指針調用一個虛函數將發生動態綁定。
double print_total(ostream &os, const A &item,size_t n){ double ret=item.net_price(n);//根據傳入的item的對象類型調用Quote::net_price,還是Bulk_Quote::net_price return ret;}一個派生類對象包含多個組成部分:一個含有派生類自己定義的(非靜態)成員的子對象,以及一個與該派生類繼承的基類的子對象。因為在派生類對象中含有與其基類對應的組成部分,所以我們能把派生類的對象當成基類對象來使用,而且我們也能把基類的指針或引用綁定到派生類對象中的基類部分上。
Quote item;//基類對象Bulk_Quote bulk;//派生類對象Quote *p=&item;//p指向基類對象p=&bulk;//p指向派生類對象的基類部分Quote &r=bulk;//r綁定到派生類對象的基類部分Quote類的定義:
派生類Bulk_Quote定義;
派生類對象的基類部分與派生類對象自己的數據成員都是在構造函數的初始化階段執行初始化操作的。派生類構造函數同樣是通過構造函數初始化列表來將實參傳遞給基類構造函數的。
每個類負責定義各自的接口。要想與類的對象交互必須使用類的接口,即使這個對象是派生類的基類部分也如此。盡管從語法上我們可以在派生類構造函數體內給它的 公有成員或受保護的基類成員賦值,但不建議這么做。
如果基類定義了一個靜態成員,則在整個繼承體系中只存在該成員的唯一定義。如果基類中的成員是private的,則派生類無權訪問它。如果某靜態成員是可訪問的,則我們既能通過基類使用它也能通過派生類使用它。
派生類的聲明:
class Bulk_Quote::public Quote;//錯誤,不需要派生列表class Bulk_ Quote;//正確如果我們想將某個類作為基類,則該類必須是已經定義而非僅聲明。
class Quote;//聲明未定義class Bulk_Quote:public Quote{};//錯誤防止繼承的發生: C++11標準提供了一種防止繼承發生的方法,即在類名后跟一個關鍵字final.
靜態類型與動態類型: 表達式的靜態類型在編譯時總是已知的,它是變量聲明時的類型或表達式生成的類型。動態類型則是變量或表達式表示的內存的對象的類型。動態類型直到運行時才知道。基類的指針或引用的靜態類型可能與其動態類型不一致,取決于傳遞的實參。
因為一個基類的對象可能是派生類對象的一部分,也可能不是,所以不存在基類向派生類的自動類型轉換。即使一個基類指針或引用綁定在一個派生類對象上,我們也不能執行從基類向派生類的轉換。
當我們用一個派生類對象為一個基類對象初始化或者賦值時,只有該派生類對象的基類部分會被拷貝、移動或賦值,它的派生類部分被忽略掉。
Bulk_Quote bulk;//派生類對象Quote item(bulk);//調用Quote::Quote(const Quote&)item=bulk;//調用Quote::Operator=(const Quote&)虛函數: 當我們使用基類引用或指針調用一個虛成員函數時會執行動態綁定。因為我們直到運行時才能知道到底調用了哪個版本的虛函數,所以所有虛函數必須有定義。
基類中的虛函數在派生類中隱含地也是一個虛函數。當派生類覆蓋了某個虛函數時,該函數在基類中的形參必須與派生類中的形參嚴格匹配。
override說明符說明派生類中的虛函數,final說明之后任何嘗試覆蓋該函數的操作都將引發錯誤。
如果我們通過基類的引用或指針調用函數,則使用基類中定義的默認實參,即使實際運行的是派生類的函數版本也是如此,即傳入派生類函數的是基類函數定義的默認實參。如果虛函數使用默認實參,則基類和派生類中定義的默認實參最好一致。
#include <iostream>class A{public: A(int v):v(v){}//A構造函數 virtual void print(int x=100)//基類虛函數默認實參 { std::cout<<x<<std::endl; }private: int v;};class B:public A//派生類 {public: B(int v):A(v){}//B構造函數 void print(int x=10) override//默認實參與基類不一致 { std::cout<<x+100<<std::endl;//傳入基類虛函數的默認實參 }private: };int main(){ B b(10); A &aptr=b; aptr.print();//200 return 0;}如果我們希望對虛函數的調用不要進行動態綁定,而是強迫其執行虛函數的某個特定版本,可以使用作用域運算符。
//強行調用基類中定義的函數版本。double undiscounted=baseP->Quote::net_price(42);如果一個派生類虛函數需要調用它的基類版本,但是沒有使用作用域運算符,則在運行時該調用將被解析為對派生類版本自身的調用,從而導致無限遞歸。
抽象基類:
我們可以在函數體的位置書寫=0來說明一個虛函數為純虛函數。=0只能出現在類內部的虛函數聲明語句處。我們可以為純虛函數提供定義,不過函數體必須定義在類外。
double net_price(std::size_t)const=0;//純虛函數含有(未經覆蓋直接繼承)純虛函數的類是抽象基類。抽象基類負責定義接口,而后續的其他類可以覆蓋該接口。我們不能直接創建一個抽象基類的對象。
新聞熱點
疑難解答