1. 程式人生 > >面試中的問題 -虛擬函式 多型

面試中的問題 -虛擬函式 多型

1、c++多型實現

C++中的虛擬函式的作用主要是實現多型的機制。關於多型,簡而言之就是用父型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。

例如:

#include <iostream>
#include <stdlib.h>
using namespace std;
class Base
{
public:
	Base(){};
	~Base(){};
	virtual void Fun()
	{
		cout<<"Base Func"<<endl;
	}
};
class SubClass:public Base
{
public:
	SubClass(){};
	~SubClass(){};
	 void Fun()
	{
		cout<<"SubClass Fun"<<endl;
	}
};
int main()
{
	SubClass sub;
	Base *pBase = ⊂//父型別的指標指向其子類的例項
	pBase->Fun();
	system("pause");
	return 0;
}

這種技術可以讓父類的指標有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的程式碼來實現可變的演算法。比如:模板技術,RTTI技術,虛擬函式技術,要麼是試圖做到在編譯時決議,要麼試圖做到執行時決議。

編譯器會為每個有虛擬函式的類建立一個虛擬函式表(編譯的時候),該虛擬函式表將被該類的所有物件共享。類的每個虛成員佔據虛擬函式表中的一行。如果類中有N個虛擬函式,那麼其虛擬函式表將有N*4位元組的大小。

動態繫結與靜態繫結

靜態繫結:編譯時繫結,通過物件呼叫 動態繫結:執行時繫結,通過地址實現 只有採用“指標->函式()”或“引用變數.函式()”的方式呼叫C++類中的
虛擬函式才會執行動態繫結。對於C++中的非虛擬函式,因為其不具備動態繫結的特徵,所以不管採用什麼樣的方式呼叫,都不會執行動態繫結。
即所謂動態繫結,就是基類的指標或引用有可能指向不同的派生類物件,對於非虛擬函式,執行時實際呼叫該函式的物件型別即為該指標或引用的靜態型別(基類型別);而對於虛擬函式,執行時實際呼叫該函式的物件型別為該指標或引用所指物件的實際型別 多重繼承時

用來做基類的類的解構函式一般都是虛擬函式。可是,為什麼要這樣做呢?

class ClxBase
{
public:
    ClxBase() {};
    virtual ~ClxBase() {};

    virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};

class ClxDerived : public ClxBase
{
public:
    ClxDerived() {};
    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }; 

    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};

ClxBase *pTest =new ClxDerived;
pTest
->DoSomething();
delete pTest;

    的輸出結果是:

Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
也就是說,類ClxDerived的解構函式根本沒有被呼叫!一般情況下類的解構函式裡面都是釋放記憶體資源,而解構函式不被呼叫的話就會造成記憶體洩漏。我想所有的C++程式設計師都知道這樣的危險性。當然,如果在解構函式中做了其他工作的話,那你的所有努力也都是白費力氣。
    所以,文章開頭的那個問題的答案就是--這樣做是為了當用一個基類的指標刪除一個派生類的物件時,派生類的解構函式會被呼叫。
    當然,並不是要把所有類的解構函式都寫成虛擬函式。因為當類裡面有虛擬函式的時候,編譯器會給類新增一個虛擬函式表,裡面來存放虛擬函式指標,這樣就會增加類的儲存空間。所以,只有當一個類被用來作為基類的時候,才把解構函式寫成虛擬函式。

為什麼建構函式不能為虛擬函式:

   因為如果建構函式為虛擬函式的話,它將在執行期間被構造,而執行期則需要物件已經建立,建構函式所完成的工作就是為了建立合適的物件,因此在沒有構建好的物件上不可能執行多型(虛擬函式的目的就在於實現多型性)的工作。在繼承體系中,構造的順序就是從基類到派生類,其目的就在於確保物件能夠成功地構建。建構函式同時承擔著虛擬函式表的建立,如果它本身都是虛擬函式的話,如何確保vtbl的構建成功呢?


多重繼承(無虛擬函式覆蓋)

下面,再讓我們來看看多重繼承中的情況,假設有下面這樣一個類的繼承關係。注意:子類並沒有覆蓋父類的函式。

 

對於子類例項中的虛擬函式表,是下面這個樣子:

 

我們可以看到:

1) 每個父類都有自己的虛表。

2) 子類的成員函式被放到了第一個父類的表中。(所謂的第一個父類是按照宣告順序來判斷的)

這樣做就是為了解決不同的父類型別的指標指向同一個子類例項,而能夠呼叫到實際的函式。

多重繼承(有虛擬函式覆蓋)

下面我們再來看看,如果發生虛擬函式覆蓋的情況。

下圖中,我們在子類中覆蓋了父類的f()函式。

 

下面是對於子類例項中的虛擬函式表的圖:

 

我們可以看見,三個父類虛擬函式表中的f()的位置被替換成了子類的函式指標。這樣,我們就可以用任一個父類指標來指向子類,並呼叫子類的f()了。如:

Derive d;

Base1 *b1 = &d;

Base2 *b2 = &d;

Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()

嘗試:通過父型別的指標(指向子類物件)訪問子類自己的虛擬函式

我們知道,子類沒有過載父類的虛擬函式是一件毫無意義的事情。因為多型也是要基於函式過載的。雖然在上面的圖中我們可以看到子類的虛表中有Derive自己的虛擬函式,但我們根本不可能使用基類的指標來呼叫子類的自有虛擬函式:

Base1 *b1 = new Derive();

b1->f1(); //編譯出錯

任何妄圖使用父類指標想呼叫子類中的未覆蓋父類的成員函式的行為都會被編譯器視為非法,所以,這樣的程式根本無法編譯通過。

但在執行時,我們可以通過指標的方式訪問虛擬函式表來達到違反C++語義的行為。

C++中虛擬繼承的概念

為了解決從不同途徑繼承來的同名的資料成員在記憶體中有不同的拷貝造成資料不一致問題,將共同基類設定為虛基類。這時從不同的路徑繼承過來的同名數據成員在記憶體中就只有一個拷貝,同一個函式名也只有一個對映。這樣不僅就解決了二義性問題,也節省了記憶體,避免了資料不一致的問題

http://blog.csdn.net/wangxingbao4227/article/details/6772579