深入理解C++三大特性之一 ——多態

深入理解C++三大特性之一 ——多態

1.多態的實現效果
多態:同樣的語句調用能呈現不一樣的表現形態;
2.多態實現的三個條件
a)繼承
b)virtual關鍵字修飾
c)父類指針指向或引用子類對象
3.多態的C++實現
通過virtual關鍵字,告訴C++編譯器對這個函數要支持多態;
不要在編譯期間根據指針類型判斷如何調用;而是要根據運行期間指針所指向的實際對象類型來判斷如何調用
4.多態的理論基礎
動態聯編PK靜態聯編。根據運行期間實際的對象類型來判斷重寫函數的調用
5.多態的重要意義
設計模式的基礎
6.實現多態的理論基礎
函數指針做函數參數

C++中多態的實現原理

1.當類中聲明虛函數時,編譯器會爲此類自動生成並維護一個虛函數表 ——VTABLE
2.VTABLE是一個存儲類虛成員函數指針的數據結構,所有virtual成員函數會被編譯器放入虛函數表中
3.存在虛函數時,每個對象中都有一個指向虛函數表的指針(vptr指針)

分析:
void run(Parent* p)
{
    p->talk();
}
if run不是virtual關鍵字修飾的虛函數,則很簡單,根據靜態聯編,由Parent類型決定調用的函數。
else run是virtual 函數,需要動態聯編。編譯器需要根據傳入的對象類型,來調用其相應的vptr指針,然後vptr指針分別指向不同的VTABLE,而基類的VATBLE 與派生類的VATBLE 裏面存儲的函數地址是不同的。

C++ 多態的實現機制的缺點
1.從效率上講,使用虛函數告知編譯器需要動態聯編必然導致效率降低。因爲普通函數的調用是編譯器在編譯期間就能確定 的,而多態需要延遲到運行期間才能根據傳入的對象類型來得到被調用函數的地址。
2.由於需要爲每個類維護一張VTABLE 及爲每個對象自動生成一個vptr指針,空間效率降低。特別是某些基類的virtual函數特別多的時候,當被繼承而又不需要重寫其功能時,造成很大浪費但又不得不維護這張龐大的VATBLE。

面試題1:有必要將所有函數都聲明爲虛函數嗎?
通過虛函數表指針vptr調用重寫函數是在程序運行時進行的,因此需要通過尋址操作才能確定真正應該調用的函數。而普通成員函數是在編譯時就確定了調用的函數。在效率上,虛函數的效率要低很多,所以沒有必要


如何證明vptr指針的存在?

思路:利用sizeof計算類的大小,加上virtual後,大小增加4。(注:默認空類的大小是1,原因是?見effective系列文章)


vptr指針初始化時機

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

class Person
{
public:
    Person()
    {
        cout << "Person---構造函數" << endl;
        talk();
    }
    virtual void talk()
    {

        cout << "Person---說人類的語言" << endl;
    }

};

class Chinese :public Person
{
public:
    Chinese()
    {
        cout << "Chinese---構造函數" << endl;
    }

    virtual void talk()
    {
        cout << "Chinese---說漢語" << endl;
    }


};
void main()
{
    Chinese xm;
    cin.get();

}

`《Inside the c++ Object model》`中指出vptr初始化的時間爲:

 After invocation of the base class constructors but before execution of user-provided code or the expansion of members initialized within the member initialization list.

意思是在所有基類構造函數之後,但又在自身構造函數或初始化列表之前。

實際上,還可以看出子類對象的vptr是被初始化了兩次: 1.先在基類的構造函數前初始化爲指向基類虛函數表(vtable)的指針 2.然後在自身構造函數前初始化爲指向自身類vtable的指針

注:vptr初始化是在初始化列表之前還是之後是跟編譯器實現有關的,在VC++6.0編譯器中是在初始化列表之後。但其肯定是在基類的構造函數之後,且在自身的構造函數之前。


多態之所以重要與強大,是在於其可以接口重用,早期設計的框架可以在不變前提下使用後期實現的代碼來呈現不同的形態或效果。

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

class Person
{
public:
    Person()
    {
        cout << "Person---構造函數" << endl;
        talk();
    }
    virtual void talk()
    {

    <span class="hljs-built_in">cout</span> &lt;&lt; <span class="hljs-string">"Person---說人類的語言"</span> &lt;&lt; endl;
}

};

class Chinese :public Person
{
public:
Chinese()
{
cout << “Chinese—構造函數” << endl;
}

<span class="hljs-keyword">virtual</span> <span class="hljs-keyword">void</span> talk()
{
    <span class="hljs-built_in">cout</span> &lt;&lt; <span class="hljs-string">"Chinese---說漢語"</span> &lt;&lt; endl;
}

};
class American :public Person
{
public:
American()
{
cout << “American—構造函數” << endl;
}

<span class="hljs-keyword">virtual</span> <span class="hljs-keyword">void</span> talk()
{
    <span class="hljs-built_in">cout</span> &lt;&lt; <span class="hljs-string">"American---說美式英語"</span> &lt;&lt; endl;
}

};


talkDifferentLanguage**早期**設計的接口,以後都不需要更改,卻可以適應**後期**不同的實現代碼 ------------------------------------------------------------ void talkDifferentLanguage(Person* p) { p->talk(); } void main() { Chinese xm; talkDifferentLanguage(&xm); American john; talkDifferentLanguage(&john); cin.get(); }

總結:多態就是爲了設計架構而存在的,沒有多態,架構設計無從談起。架構設計時不關心後期實現細節,只提供統一接口。會動態根據具體對象特性調用對象自身的功能實現提供的接口。理解多態是從事架構設計師的必經之路。





發佈了11 篇原創文章 · 獲贊 38 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章