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