C 中虛函式功能的實現機制

2021-08-31 09:40:42 字數 3910 閱讀 5382

c++中虛函式功能的實現機制

要理解c++中虛函式是如何工作的,需要回答四個問題。

1、  什麼是虛函式。

虛函式由於必須是在類中宣告的函式,因此又稱為虛方法。所有以virtual修飾符開始的成員函式都成為虛方法。此時注意是virtual修飾的成員函式不是virtual修飾的成員函式名。

例如:基類中定義:

virtual void show();           //由於有virtual修飾因此是虛函式

voidshow(int);          //雖然和前面宣告的show虛函式同名,但不是虛函式。

所有的虛函式位址都會放在所屬類的虛函式表vtbl中。另外在基類中宣告為虛函式的成員方法,到達子類時仍然是虛函式,即使子類中重新定義基類虛函式時未使用virtual修飾,該函式位址仍會放在子類的虛函式表vtbl中。

2、  正確區分過載、重寫和隱藏。

注意三個概念的適用範圍:處在同乙個類中的函式才會出現過載。處在父類和子類中的函式才會出現重寫和隱藏。

過載:同一類中,函式名相同,但引數列表不同。

重寫:父子類中,函式名相同,引數列表相同,且有virtual修飾。

隱藏:父子類中,函式名相同,引數列表相同,但沒有virtual修飾;

或:函式名相同,引數列表不同,無論有無virtual修飾都是隱藏。

例如:基類中:(1)    virtual void show();           //是虛函式

(2)    void show(int);          //不是虛函式

子類中:(3)    void show();                        //是虛函式

(4)    void show(int);          //不是虛函式

1,2構成過載,3,4構成過載,1,3構成重寫,2,4構成隱藏。另外2,3也會構成隱藏,子類物件無法訪問基類的void show(int)成員方法,但是由於子類中4的存在導致了子類物件也可以直接呼叫void show(int)函式,不過此時呼叫的函式不在是基類中定義的void show(int)函式2,而是子類中的與3過載的4號函式。

3、  虛函式表是如何建立和繼承的。

基類的虛函式表的建立:首先在基類宣告中找到所有的虛函式,按照其宣告順序,編碼0,1,2,3,4……,然後按照此宣告順序為基類建立乙個虛函式表,其內容就是指向這些虛函式的函式指標,按照虛函式宣告的順序將這些虛函式的位址填入虛函式表中。例如若show放在虛函式宣告的第二位,則在虛函式表中也放在第二位。

對於子類的虛函式表:首先將基類的虛函式表複製到該子類的虛函式表中。若子類重寫了基類的虛函式show,則將子類的虛函式表中存放show的函式位址(未重寫前存放的是父類的show虛函式的函式位址)更新為重寫後函式的函式指標。若子類增加了一些虛函式的宣告,則將這些虛函式的位址加到該類虛函式表的後面。

4、  虛函式表是如何訪問的。

當執行pbase->show()時,要觀察show在base基類中宣告的是虛函式還是非虛函式。若為虛函式將使用動態聯編(使用虛函式表決定如何呼叫函式),若為非虛函式則使用靜態聯編(根據呼叫指標pbase的型別來確定呼叫哪個類的成員函式)。此處假設show為虛函式,首先:由於檢查到pbase指標型別所指的類base中show定義為虛函式,因此找到pbase所指的物件(有可能是base型別也可能是extend型別。),訪問物件得到該物件所屬類的虛函式表位址。其次:查詢show在base類中宣告的位置在base類中所有虛函式宣告中的位序。然後到pbase所指物件的所屬類(有可能是extend哦,多型)的虛函式表中訪問該位序的函式指標,從而得到要執行的函式。

例如:基類base::virtualvoid show();                 (1)

子類extend::virtualvoid show();             (2)

externext;

base*pbase=&ext;

pbase->show();

當執行pbase->show();時首先到base中檢視show(),發現其為虛函式,然後訪問pbase指向的ext物件,在物件中得到extend類的虛函式表,在base類宣告中找到show()宣告的位序0,訪問extend類的虛函式表的位置0,得到show的函式位址。注意若只有基類定義了virtual void show();而子類未重寫virtual void show();即上面的函式(2),則extend虛函式表中的位序0中存放的位址仍然是base類中定義的virtual void show()函式,而若extend類中重寫了base類中的virtual void show()方法,則extend的虛函式表中位序0的函式位址將被更新為extend中新重寫的函式位址。從而呼叫pbase->show()時將產生多型的現象。

總結:當呼叫pbase->show();時,執行的步驟:

1,  判斷base類中show是否為虛函式。

2,  若不是虛函式則找到pbase所指向的物件所屬類base。執行base::show()。若是虛函式則執行步驟3.

3,  訪問pbase所指物件的虛函式表指標得到pbase所指物件所在類的虛函式表。

4,  查詢base中show()在宣告時的位序為x,到步驟3得到的虛函式表中找到位序x,從而得到要執行的show的函式位址。

5,  根據函式位址和base中宣告的show的函式型別(形參和返回值)訪問位址所指向的函式。

以上為虛函式的工作機制。

注意只有用virtual修飾的成員方法才會放到虛函式表中去。

子類對父類函式的隱藏將導致無法通過子類物件訪問基類的成員方法。

因此給出以下建議:

1、  若要在子類中重新定義父類的方法(有virtual為重寫,無virtual為隱藏),則應確保子類中的函式宣告和父類函式宣告中的形參完全一樣。但返回值型別是基類引用/指標的成員函式在重新定義時可以返回子類的引用/指標(返回值協變),這是由於子類的物件可以賦給基類引用/指標。

2、  若基類中宣告了函式的過載版本,則在派生類中重新定義時應該重新定義所有基類的過載版本。這是因為,重新定義乙個函式,其他的基類過載版本將被隱藏,導致子類無法使用這些基類的成員方法。所以需要每個都重新定義。若一些父類的過載版本,子類確實不需要修改,則由於重新定義了乙個過載版本,即使有些過載版本不需要修改也要重新定義,在定義體中直接呼叫基類的成員方法(使用作用於限定符訪問)。

3、  從虛函式的實現機制可以看到要想在子類中實現多型需要滿足三個重要的條件。(1)在基類中函式宣告為虛函式。(2)在子類中,對基類的虛函式進行了重寫。(3)基類的指標指向了子類的物件。

隱藏測試: 子類中只定義了show(),那麼子類物件將不能訪問父類的show(int)

#includeusing namespace std;

class parent

parent::show()

parent::show()2

多型 父類中定義了虛函式show,查詢show在base類中宣告的位置在父類中所有虛函式宣告中的位序。然後找到p所指物件的所屬類的虛函式表中訪問該位序的函式指標,從而得到要執行的函式。

#includeusing namespace std;

class parent

child::show()

parent::show()2

多型 如果子類中沒有重新父類方法,那麼複製父類的虛函式表

#includeusing namespace std;

class parent

child::show()

parent::show()2

多型 如果子類重寫了父類的虛函式,那麼通過p指標所在類的該虛函式的位序訪問子類中的對應的函式,如果子類重寫的是非虛函式,那麼通過指標p只能訪問p所在類的該函式

#includeusing namespace std;

class parent

grandchild::show()

parent::show()2

C 中虛函式功能的實現機制

要理解c 中虛函式是如何工作的,需要回答四個問題。1 什麼是虛函式。虛函式由於必須是在類中宣告的函式,因此又稱為虛方法。所有以virtual修飾符開始的成員函式都成為虛方法。此時注意是virtual修飾的成員函式不是virtual修飾的成員函式名。例如 基類中定義 virtual void show...

C 虛函式實現機制

看完之後,對c 中的虛函式實現機制算的上是恍然大悟,但是個人感覺博文中有幾點不足之處,現在一一枚舉,以下語言僅僅代表個人看法 1.定位虛表的方式 大家都知道含有虛函式的類的例項裡面前4個位元組是虛函式指標占用的記憶體,裡面填充的是虛函式表的位址號。原博文中通過乙個long型別的物件取得前四個位元組的...

c 虛函式實現機制

1 c 實現多型的方法 其實很多人都知道,虛函式在c 中的實現機制就是用虛表和虛指標,但是具體是怎樣的呢?從more effecive c 其中一篇文章裡面可以知道 是每個類用了乙個虛表,每個類的物件用了乙個虛指標。具體的用法如下 class a class b public a a,b的實現省略 ...