Posted on 2008-07-16 21:15
softgamer 閱讀(412)
評論(0) 編輯 收藏 引用
我不想在這里講整個關(guān)于C++中的多態(tài)性機制,因為我自己描述完后,90%會誤人子弟,其實那些資料你可以在
網(wǎng)上搜索到一大堆。 我只想著重講一下我對于多態(tài)性中的動態(tài)多態(tài)性的一些使用總結(jié)。
先簡單的說說多態(tài)性:不是說千手觀音。
所謂多態(tài)性,簡單地說就是一個名稱,但是具有多種語義。多態(tài)性的發(fā)明有什么意義呢?考慮一下,現(xiàn)在你想 玩球。但是有很多種
球,什么足球,網(wǎng)球,籃球,具體有多少種球類,請你登錄奧運網(wǎng)站查閱。程序必須去根 據(jù)不同的球的類別給
你選擇。于是你用 switch
來根據(jù) 不同的情況來選擇不同的球。但是這樣做已經(jīng)過時了,意 識到?jīng)]有,它一點擴展性都沒有,一但需要 加入新的球的類別,你就
不得不去修 改代碼----而不是擴展代碼。
采用多態(tài)性就不同了,利用多態(tài)性,你可以把一大堆的case語句替換為一個Ball函數(shù)。這就是它的神奇功能。
多態(tài)性分為靜態(tài)多態(tài)性和動態(tài)多態(tài)性。
函數(shù)重載可以實現(xiàn)靜態(tài)多態(tài)性,而要實現(xiàn)動態(tài)多態(tài)性就可以用----虛函數(shù)(virtual function),這也牽扯到繼
承。
1.如果以一個基礎(chǔ)類別的指針指向一個衍生類別的物件,那么經(jīng)由此指針,你就只能呼叫基礎(chǔ)類別(而不是衍
生類別)所定義的函數(shù)。
2.如果你以一個衍生類別的指針,指向一個基礎(chǔ)類別的物件,你必須先做明顯的轉(zhuǎn)型動作(explicit cast),
這種作法很危險,不符合真實生活經(jīng)驗,在程序設(shè)計上也會帶給程序員困惑。
3.如果基礎(chǔ)類別和衍生類別都定義了相同名稱的成員函數(shù),那么透過物件指針呼叫成員函數(shù)時,到底呼叫哪一
個函數(shù),必須視該指針的原始類型而定,而不是視指針之實際所指之物件的類型而定。
摘自《深入淺出MFC》
好了,這些死板的遣詞造句就到這里了,下面著重看一下虛函數(shù)在多態(tài)性中的是怎么表現(xiàn)的。
以下繼承論述的都是public繼承。
#include <iostream>
using namespace std;
class CBallBase
{
public:
void Ball()
{
cout << "CBallBase::Ball was selected" << endl;
}
};
class CBallDerive:public CBallBase
{
public:
void Ball()
{
cout << "CBallDerive::Ball was selected"<< endl;
}
void Ball2()
{
cout << "CBallDerive::Ball2 was selected" << endl;
}
};
int main()
{
CBallDerive bd;
CBall b;
CBall *p=&bd;
p->Ball(); //result:CBall::Ball was selected if no virtual function affix
//p->Ball2(); // error C2039: 'Ball2' : is not a member of 'CBall'
//CBallDerive *p2=&b; // error C2440: 'initializing' :.....不能自動轉(zhuǎn)換
CBallDerive*p2=(CBallDerive*)&b;
p2->Ball(); //result:CBallDerive ::Ball was selected
p2->Ball2(); //correct
b=bd; //correct 足球是球
//b.Ball2(); //error C2039: 'Ball2' : is not a member of 'CBallBase'
//bd=b; //error 球不是足球
return 0;
}
以上代碼執(zhí)行結(jié)果:
CBall::Ball was selected
CBallDerive::Ball was selected
CBallDerive::Ball2 was selected
代碼基本上反映了上面三點,那么為什么基類指針能夠名正言順地指向其派生類對象呢(甚至可以把一個
派生類對象賦值給一個基類對象)?原因:派生類以public繼承基類時,由于包含了基類所有的元素(private
除外),所有的派生類對象同時也是基類對象。另一方面,基類對象就不能賦值給派生類對象,但是如果是指 針的話,我們可以強制 轉(zhuǎn)換。那為什么說“不符合真實生活經(jīng)驗,在程式設(shè)計上也會帶給程式員困惑。”呢?
正如侯先生論述的,物件導(dǎo)向觀念是描繪現(xiàn)實世界
用的。水果類經(jīng)過添加各種特征(屬性或方法)派生出蘋果類。我們可以說蘋果是水果,但是卻不能說水果就
是蘋果的。
看這一句://b.Ball2(); //error C2039: Ball2' : is not a member of 'CBallBase'
這里就涉及到切割(object slicing),
看這段話你就會明白為什么它會出錯:“衍生物件通常都比基類物件大(記憶體空間),因為衍生物件不但繼承
其基礎(chǔ)類別的成員,又有自己的成員。那么所謂的upcasting(向上強制轉(zhuǎn)型)將會造成物件的內(nèi)容被切割(object
slicing)。”
在這里我要提取出第三點,先放在這里:如果基礎(chǔ)類別和衍生類別都定義了相同名稱之成員函數(shù),那么透過物
件指標(biāo)呼叫成員函數(shù)時,到底呼叫哪一個函數(shù),必須視該指標(biāo)的原始型別而定,而不是視指標(biāo)之實際所指之物
件的型別而定。
//////////////////////////////////////////////////////////////////////////////////////////////////////
接下來讓我們看,當(dāng)虛擬函數(shù)參合進來時,那些指針啊對象啊之類會出現(xiàn)什么情況。
再在Cbase類的void Ball()前加上virtual, 然后結(jié)果就變成了
CBall *p=&bd;
p->Ball(); //result:CBall::Ball was selected if without virtual function affix
=====》
CBallDerive::Ball was called with virtual affix
原來的結(jié)果是
CBall::Ball was selected without virtual affix
CBallDerive*p2=(CBallDerive*)&b;
p2->Ball(); //result:CBallDerive ::Ball was selected
=====》
CBase::Ball was called with virtual affix
原來的結(jié)果是
CBallDerive::Ball was selected without virtual affix
p2->Ball2(); //correct
=====》
CBallDerive::Ball2 was called
原來的結(jié)果是
CBallDerive::Ball2 was selected
當(dāng)在基類中不使用virtual聲明會在其派生類中出現(xiàn)同名的Ball()函數(shù)時,如果基礎(chǔ)類別和衍生類別都定義了相同
名稱Ball(),那么透過物件指針呼叫成員函數(shù)時,到底呼叫哪一個函數(shù),必須視該指針的原始類
型而定,這里的原始類型指的是CBallDerive*p2,而不是視指針之實際所指之物件(CBallDerive*)&b 的類型而定;
當(dāng)在基類中使用virtual聲明會在其派生類中出現(xiàn)同名的Ball()函數(shù)時,如果基礎(chǔ)類別和衍生類別都定義了相
同名稱Ball()成員函數(shù)(且基類中的該函數(shù)被定義為virtual),那么透過物件指針呼叫成員函數(shù)時,到底呼
叫哪一個函數(shù),必須視該指針之實際所指之物件CBallDerive &bd的類型而定,而不是視指標(biāo)的原始型CBall
*p別而定!