C++中的多態與虛函數的內部實現方法_C 語言

來源:互聯網
上載者:User

1、什麼是多態

多態性可以簡單概括為“一個介面,多種行為”。

也就是說,向不同的對象發送同一個訊息, 不同的對象在接收時會產生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應共同的訊息。所謂訊息,就是調用函數,不同的行為就是指不同的實現,即執行不同的函數。這是一種泛型技術,即用相同的代碼實現不同的動作。這體現了物件導向編程的優越性。

多態分為兩種:

(1)編譯時間多態:主要通過函數的重載和模板來實現。

(2)運行時多態:主要通過虛函數來實現。

2、幾個相關概念

(1)覆蓋、重寫(override)

override指基類的某個成員函數為虛函數,衍生類別又定義一成員函數,除函數體的其餘部分都與基類的成員函數相同。注意,如果只是函數名相同,形參或傳回型別不同的話,就不能稱為override,而是hide。

(2)重載(overload)

指同一個範圍出生多個函數名相同,但是形參不同的函數。編譯器在編譯的時候,通過實參的個數和類型,選擇最終調用的函數。

(3)隱藏(hide)

分為兩種:

1)局部變數或者函數隱藏了全域變數或者函數
2)衍生類別擁有和基類同名的成員函數或成員變數。

產生的結果:使全域或基類的變數、函數不可見。

3、幾個簡單的例子

/****************************************************************************************************** * File:PolymorphismTest * Introduction:測試多態的一些特性。 * Author:CoderCong* Date:20141114 * LastModifiedDate:20160113 *******************************************************************************************************/ #include "stdafx.h" #include <iostream> using namespace std; class A { public:   void foo()   {     printf("1\n");   }   virtual void fun()   {     printf("2\n");   } }; class B : public A { public:   void foo() //由於基類的foo函數並不是虛函數,所以是隱藏,而不是重寫   {     printf("3\n");   }   void fun() //重寫   {     printf("4\n");   } }; int main(void) {   A a;   B b;   A *p = &a;   p->foo(); //輸出1。   p->fun(); //輸出2。   p = &b;   p->foo(); //輸出1。因為p是基類指標,p->foo指向一個具有固定位移量的函數。也就是基類函數   p->fun(); //輸出4。多態。雖然p是基類指標,但實際上指向的是一個子類對象。p->fun指向的是一個虛函數。按照動態類型,調用子類函數     return 0; }

4、運行時多態以及虛函數的內部實現

看了上邊幾個簡單的例子,我恍然大悟,原來這就是多態,這麼簡單,明白啦!

好,那我們再看一個例子:

class A { public:   virtual void FunA()   {     cout << "FunA1" << endl;   };   virtual void FunAA()   {     cout << "FunA2" << endl;   } }; class B { public:   virtual void FunB()   {     cout << "FunB" << endl;   } }; class C :public A, public B { public:   virtual void FunA()   {     cout << "FunA1C" << endl;   }; }; int _tmain(int argc, _TCHAR* argv[]) {   C objC;   A *pA = &objC;   B *pB = &objC;   C *pC = &objC;    printf("%d %d\n", &objC, objC);   printf("%d %d\n", pA, *pA);   printf("%d %d\n", pB, *pB);   printf("%d %d\n", pC, *pC);    return 0; }

運行結果:

5241376 1563032

5241376 1563032

5241380 1563256

5241376 1563032

細心的同志一定發現了pB出了問題,為什麼明明都是指向objC的指標,pB跟別人的值都不一樣呢?

是不是編譯器出了問題呢?

當然不是!我們先講結論:

(1)每一個含有虛函數的類,都會產生虛表(virtual table)。這個表,記錄了對象的動態類型,決定了執行此對象的虛成員函數的時候,真正執行的那一個成員函數。

(2)對於有多個基類的類對象,會有多個虛表,每一個基類對應一個虛表,同時,虛表的順序和繼承時的順序相同。

(3)在每一個類對象所佔用的記憶體中,虛指標位於最前邊,每個虛指標指向對應的虛表。

先從簡單的單個基類說起:

class A { public:   virtual void FunA()   {     cout << "FunA1" << endl;   }   virtual void FunA2()   {     cout << "FunA2" << endl;   } };  class C :public A {   virtual void FunA()   {     cout << "FunA1C" << endl;   }}; int _tmain(int argc, _TCHAR* argv[]) {   A *pA = new A;   C *pC = new C;   typedef void (*Fun)(void);    Fun fun= (Fun)*((int*)(*(int*)pA));   fun();//pA指向的第一個函數   fun = (Fun)*((int*)(*(int*)pA) +1);   fun();//pA指向的第二個函數      fun = (Fun)*((int*)(*(int*)pC));   fun();//pC指向的第一個函數   fun = (Fun)*((int*)(*(int*)pC) + 1);   fun();//pC指向的第二個函數   return 0; }

運行結果:

FunA1
FunA2
FunA1C
FunA2
是不是有點暈?沒關係。我一點一點解釋:pA對應一個A的對象,我們可以畫出這樣的一個表:
      
這就是對象*pA的虛表,兩個虛函數以聲明順序排列。pA指向對象*pA,則*(int*)pA指向此虛擬表,則(Fun)*((int*)(*(int*)pA))指向FunA,同理,(Fun)*((int*)(*(int*)pA) + 1)指向FunA2。所以,出現了前兩個結果。
根據後兩個結果, 我們可以推測*pC的虛表如下圖所示:
      
也就是說,由於C中的FunA重寫(override)了A中的FunA,虛擬表中虛擬函數的地址也被重寫了。
就是這樣,這就是多態實現的內部機制。
我們再回到最初的問題:為什麼*pB出了問題。
根據上邊的結論,我們大膽地進行猜測:由於C是由A、B派生而來,所以objC有兩個虛擬表,而由於表的順序,pA、pC都指向了對應於A的虛擬表,而pB則指向了對應於B的虛擬表。做個實驗來驗證我們的猜想是否正確:
我們不改變A、B、C類,將問題中的main改一下:
int _tmain(int argc, _TCHAR* argv[]) {   C objC;   A *pA = &objA;   B *pB = &objC;   C *pC = &objC;      typedef void (*Fun)(void);    Fun fun = (Fun)*((int*)(*(int*)pC));   fun();//第一個表第一個函數   fun = (Fun)*((int*)(*(int*)pC)+1);   fun();//第一個表第二個函數   fun = (Fun)*((int*)(*((int*)pC+1)));   fun();<span style="white-space:pre"> </span>//第二個表第一個函數   fun = (Fun)*((int*)(*(int*)pB));   fun();//pB指向的表的第一個函數   return 0; }

哈哈,和我們的猜測完全一致:

FunA1C
FunA2
FunB
FunB
我們可以畫出這樣的虛函數圖:
        
暫且這樣理解,編譯器執行B *pB = &objC時不是僅僅是賦值,而是做了相應的最佳化,將pB指向了第二張虛表。
說了這麼多,我是只是簡單地解釋了虛函數的實現原理,可究竟對象的內部的記憶體布局是怎樣的?類資料成員與多個虛表的具體記憶體布局又是怎樣的?編譯器是如何在賦值的時候作了最佳化的呢?我在以後的時間裡會講一下。

以上就是小編為大家帶來的C++中的多態與虛函數的內部實現方法全部內容了,希望大家多多支援雲棲社區~

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.