多態(1)靜態多態與動態多態

多態(1)靜態多態與動態多態

什么是多態

從字面上理解就是多種形態的意思。而多態一詞最初源自希臘語,其含義便是“多種形式”,意思是是具有多種形式或形態的情形,在C++語言中多態有著更廣泛的含義。在C++ primer一書中把具有繼承關系的多個類型稱為多態類型,因為我們能使用這些類型的“多種形式”而無須在意它們的差異。百度百科上提到在面向對象語言中,接口的多種不同的實現方式即為多態。引用Charlie Calverts對多態的描述——多態性是允許你將父對象設置成為一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。多態性在Object Pascal和C++中都是通過虛函數實現的。

只從概念描述是無法深刻清晰的理解它的,下面我們就具體分析一下。

1、對象類型

這里所說的對象類型可以用下面的圖來體現:

我們通過代碼舉例說明一下

1 class Derived1:public Base 2 {}; 3 class Derived2:public Base 4 {}: 5 int main() 6 { 7? ? ? Derived1* p1 = new Derived1; 8? ? ? Base = p1; 9? ? ? Derived2* p2 = new Derived1;10? ? ? Base = p2;11? ? ? return = p212 }

靜態類型在編譯時總是已知的,他是變量聲明時的類型或表達式生成的類型;動態類型則是變量或表達式表示的內存中的對象的類型。動態類型直到運行時才可知道。

2、靜態多態與動態多態

靜態多態和動態多態的區別其實用下面的圖就可以體現:

靜態多態

靜態多態也稱為靜態綁定或早綁。編譯器在編譯期間完成的, 編譯器根據函數實參的類型(可能會進行隱式類型轉換) , 可推斷出要調用那個函數, 如果有對應的函數就調用該函數, 否則出現編譯錯誤。

1 int Add(int left,int right) 2 { 3? ? ? return left + right; 4 } 5 float Add(float left, float right) 6 { 7? ? ? return left + right; 8 } 9 int main()10 {

11? ? cout<

這里我把重載歸為了靜態多態。重載的實現是:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然后這些同名函數就成了不同的函數(至少對于編譯器來說是這樣的)。函數的調用,在編譯器間就已經確定了,是靜態的(記住:是靜態)。也就是說,它們的地址在編譯期就綁定了(早綁定)。正是由于重載的這種性質,也有結論認為:重載只是一種語言特性,與多態無關,與面向對象也無關。基于面向對象來說重載的概念并不屬于“面向對象編程”。但是多態性又是一個比較廣泛的概念。這里為了便于理解先不去深度分析它們的區別,等多態的其它部分具體分析完畢,我們再來說。

動態多態

動態綁定: 在程序執行期間(非編譯期) 判斷所引 用對象的實際類型, 根據其實際類型調用相應的方法。使用virtual關鍵字修飾類的成員 函數時, 指明該函數為虛函數, 派生類需要重新實現, 編譯器將實現動態綁定。

1classCBase2{3public:4virtualvoidFunTest1(int_iTest) {5cout <<"CBase: : FunTest1()"<FunTest1(0);35pBase->FunTest2(0);36pBase->FunTest3(0);37pBase->FunTest4(0);

37 ? ? delete pBase;38getchar();39return0;40}

當我們使用基類的指針或引用調用基類中定義的一個函數時時,我們并不知道該函數真正的對象是什么類型,因為他可能是一個基類的對象,也可能是一個派生類的對象。如果該函數是虛函數,則直到運行時才會知道到及執行哪個版本,判斷的依據是引用或指針所綁定的對象的真實類型。

析構函數、靜態類型函數、友元函數、內聯函數與虛函數

1classCTest2{3publi c:4/*virtual*/CTest()5{}6/*virtual*/stati c i nt FunTest()7{}8/*virtual*/CTest&operator=(constCTest&_test) {9return*thi s;10}11/*virtual*/fri end voi d FunTestFri end() ;

12 }

什么是虛函數

其實在前面的虛擬繼承中我們已經用到了虛函數這個概念,在那里我們是為了解決菱形普通繼承中訪問二義性的問題,但在多態中,他有更大的作用。百度百科中對虛函數是這么說的:在某基類中聲明為 virtual 并在一個或多個派生類中被重新定義的成員函數,用法格式為:virtual 函數返回類型 函數名(參數表) {函數體};實現多態性,通過指向派生類的基類指針或引用,訪問派生類中同名覆蓋成員函數。形象的解釋為“求同存異”,它的作用就是實現多態性。

簡單地說,那些被virtual關鍵字修飾的成員函數,就是虛函數。虛函數的作用,用專業術語來解釋就是實現多態性(Polymorphism),多態性是將接口與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異,而采用不同的策略。

1 class A 2 { 3? ? public: 4? ? ? ? virtual void print(){cout<<"This is A"<print();18? ? p2->print();19? ? return 0;20 }

毫無疑問,class A的成員函數print()已經成了虛函數,那么class B的print()成了虛函數了嗎?回答是Yes,我們只需在把基類的成員函數設為virtual,其派生類的相應的函數也會自動變為虛函數。所以,class B的print()也成了虛函數。那么對于在派生類的相應函數前是否需要用virtual關鍵字修飾,那就是你自己的問題了(語法上可加可不加,不加的話編譯器會自動加上,但為了閱讀方便和規范性,建議加上)。

運行代碼,輸出的結果是This is A和This is B。

總結:指向基類的指針在操作它的多態類對象時,會根據不同的類對象,調用其相應的函數,這個函數就是虛函數。

析構函數與虛函數

當在析構函數前面加virtual關鍵字時報錯:

,我們來分析一下原因。

1、虛函數的執行依賴于虛函數表。而虛函數表在構造函數中進行初始化工作,即初始化vptr,讓他指向正確的虛函數表。而在構造對象期間,虛函數表還沒有被初 始化,將無法進行。

2、構造一個對象的時候,必須知道對象的實際類型,而虛函數行為是在運行期間確定實際類型的。而在構造一個對象時,由于對象還未構造成功。編譯器無法知道對象 的實際類型,是該類本身,還是該類的一個派生類,或是更深層次的派生類。無法確定。

虛函數的意思就是開啟動態綁定,程序會根據對象的動態類型來選擇要調用的方法。然而在構造函數運行的時候,這個對象的動態類型還不完整,沒有辦法確定它到底是什么類型,故構造函數不能動態綁定。(動態綁定是根據對象的動態類型而不是函數名,在調用構造函數之前,這個對象根本就不存在,它怎么動態綁定?)

編譯器在調用基類的構造函數的時候并不知道你要構造的是一個基類的對象還是一個派生類的對象。

靜態類型函數與虛函數

當我們在靜態類型函數前加virtual關鍵字時報錯:

分析:

1、 static成員不屬于任何類對象或類實例,所以即使給此函數加上virutal也是沒有任何意義的。

2、static函數沒有this指針,并且不會進入虛函數表的。當通過指針或者引用調用時根本無法把this指針傳遞給static函數,從而無法體現出多態。靜態成員函數與普通成員函數的差別就在于缺少this指針,沒有這個this指針自然也就無從知道name是哪一個對象的成員了。

友元函數與虛函數

當我們在友元函數前加virtual關鍵字時報錯:

因為C++不支持友元函數的繼承,對于沒有繼承特性的函數沒有虛函數的說法。

內聯成員函數與虛函數

內聯函數就是為了在代碼中直接展開,減少函數調用花費的代價,虛函數是為了在繼承后對象能夠準確的執行自己的動作,這是不可能統一的。(再說了,inline函數在編譯時被展開,虛函數在運行時才能動態的邦定函數)

賦值運算符的重載與虛函數

當我們把賦值運算符的重載定義為虛函數時編譯可以通過,但是一般不建議這么做雖然可以將operator=定義為虛函數, 但使用時容易混淆。

1、無法給派生類的自有成員賦值;

2、調用虛函數要進行查虛表等一系列操作,效率下降。

析構函數與虛函數

析構函數設為虛函數的作用:在類的繼承中,如果有基類指針指向派生類,那么用基類指針delete時,如果不定義成虛函數,派生類中派生的那部分無法析構。

1 #include 2 #include 3 class A 4 { 5? ? public: 6? ? ?A(); 7? ? virtual~A(); 8 }; 9? ? ?A::A()10? ? ?{}11? ? A::~A()12? ? ?{13? ? printf("Delete class APn");14? ? ?}15 class B : public A16 {17? ? public:18? ? ?B();19? ? ~B();20 };21? ? ?B::B()22? ? ?{ }23? ? B::~B()24? ? ?{25? ? printf("Delete class BPn");26? ? ?}27 int main(int argc, char* argv[])28 {29? ? A *b=new B;30? ? delete b;31? ? return 0;32 }

輸出結果為:Delete class B ? ??Delete class A

如果把A的virtual去掉:那就變成了Delete class A也就是說不會刪除派生類里的剩余部分內容,也即不調用派生類的虛函數

析構函數總結:

1. 如果我們定義了一個構造函數,編譯器就不會再為我們生成默認構造函數了。

2. 編譯器生成的析構函數是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。

3. 有虛函數的類,幾乎可以確定要有個虛析構函數。

4. 如果一個類不可能是基類就不要申明析構函數為虛函數,虛函數是要耗費空間的。

5. 析構函數的異常退出會導致析構不完全,從而有內存泄露。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。

6. 在構造函數不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到派生類中,既是采用的靜態綁定。顯然的是:當我們構造一個子類的對象時,先調用基類的構造函數,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那么寫程序,你這么寫,編譯器也不會報錯。只是你如果這么寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。

7.在析構函數中也不要調用虛函數。在析構的時候會首先調用子類的析構函數,析構掉對象中的子類部分,然后在調用基類的析構函數析構基類部分,如果在基類的析構函數里面調用虛函數,會導致其調用已經析構了的子類對象里面的函數,這是非常危險的。

8. 記得在寫派生類的拷貝函數時,調用基類的拷貝函數拷貝基類的部分。

總結:

1、 派生類重寫基類的虛函數實現多態, 要求函數名 、 參數列表、 返回值完全相同。 (協變除外)。

2、 基類中定義了 虛函數, 在派生類中該函數始終保持虛函數的特性。

3、 只 有類的非靜態成員 函數才能定義為虛函數, 靜態成員 函數不能定義為虛函數。

4、 如果在類外定義虛函數, 只 能在聲明函數時加virtual關鍵字, 定義時不用加。

5、 構造函數不能定義為虛函數, 雖然可以將operator=定義為虛函數, 但最好不要這么做, 使用時容易混淆。

6、 不要在構造函數和析構函數中調用虛函數, 在構造函數和析構函數中, 對象是不完整的, 可能會出現未定義的行為。

7、 最好將基類的析構函數聲明為虛函數。 ( 因為派生類的析構函數跟基類的析構函數名稱不一樣, 但是構成覆蓋, 這里編譯器做了特殊處理)

8、 虛表是所有類對象實例共用的。

歡迎加入技術QQ群:364595326

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • 1. 讓自己習慣C++ 條款01:視C++為一個語言聯邦 為了更好的理解C++,我們將C++分解為四個主要次語言:...
    Mr希靈閱讀 2,834評論 0 13
  • C++ 面向對象編程 博客園地址:http://www.cnblogs.com/xiongxuanwen/p/42...
    先之閱讀 687評論 0 1
  • 自己問自己,什么是孤單 孤單是一種狀態,一種心情,還是一種習慣 其實我更傾向于孤單是一種習慣,思維上的習慣 例如你...
    SwaggyP閱讀 169評論 0 1
  • 翻譯對比 1. The former bookseller accounts for more than half...
    潘慧_06b3閱讀 1,493評論 0 0
  • 讀著斑馬的文字,不由的在腦海里構想這是怎樣的一個女子? 口齒伶俐至極,讓我想起那個毒舌評委金星,語言犀利到能...
    暖暖的燕窩閱讀 619評論 0 2