地球人都知道物件導向的三個主要特徵是封裝,繼承,多態.前面兩個概念顧名思義,比較容易理解,封裝就是所有的東東都給封裝到一個個的class中,並通過public,private等存取修飾詞提供一些許可權控制.繼承嘛就是子類能擁有父類的非私人的成員變數和函數,就像你能繼承你老子的財產一樣啊.多態光看名字可不好理解,可沒變態那麼通俗易懂啊.其實前面說的三個特性還有另外的版本,比如:資料抽象,繼承,動態綁定.說法不一樣但表示的意思完全一樣.
多態可以簡單的理解為有同一種東西有多種形態,比如雨,雪,冰都是水的不同形態.在代碼裡體現就是調用相同的函數但函數實際上執行的是不同的操作.而實現這種功能的最主要手段是虛函數,當然不一定非得是它,用介面也一樣.只不過大多數時候還是用虛函數實現多態多一點.
C#虛函數和多態
虛函數就是在普通的函數前面加個關鍵字virtual就行了.如果虛函數沒用來實現多態的話完全可以當作一般的函數用,沒啥區別
下面舉個例子,有父親Father,有子類Son,Daughter
class Father
{
public virtual void ShowMsg() //這就是虛函數
{
Console.WriteLine("This is father.");
}
}
class Son :Father
{
/*此處加個override到後面就能實現多態,多態就是virtual和override的搭配使用*/
public override void ShowMsg()
{
Console.WriteLine("This is Son.");
}
}
class Daughter :Father
{
/*此處沒用override而是用new,其實也可以去掉new,因為預設就是當作添加個new在那,當然其實還可以用virtual,這樣如果誰繼承了此類還可以通過配合override來實現多態*/
public new void ShowMsg()
{
Console.WriteLine("This is Daughter.");
}
}
測試函數
Public void ShowYourName(Father fa)
{
fa.ShowMsg();
}
static void Main(string[] args)
{
Fahter father = new Father();
Father son = new Son();
Father daughter = new Daughter();
ShowYourName(father ); //結果this is father
ShowYourName(son); //結果this is son
ShowYourName(daughter ); /*結果this is father.上面的兩個函數是體現了多態,但Daughter類中沒用override去重寫函數,所以不能實現多態.*/
}
由上面可知當調用相同的函數ShowYourName()時執行了不同的操作.這就是多態.其實覺得還是用動態綁定這術語更容易理解點,就是函數的參數類型fa只有在運行時才真正確定它的類型.因為子類is a 特殊的父類,所以可以先用父類來代替子類的類型.不過C#裡面沒指標的概念,不容易解釋清楚,等會舉C++的例子時用指標解釋更容易明白.
如果要不用虛函數而是用介面或抽象來也一樣可以實現多態.
比如public abstract class Father
{
public abstract void ShowMsg();
}
public interface Father
{
void ShowMsg();
}
只不過如果是繼承抽象函數必須用override來重寫,不能用new.繼承介面的話直接像普通函數一樣重新定義下就行,只不過注意修飾符必須是public.
另外這兩者自然不能直接Father fa = new Father()這樣執行個體化.其他動作就都一樣了.也一樣實現多態了.
C++虛函數與多態
假如有父類Father,有子類Son,Duaghter.
class Father
{
public :
int age;
virtual void ShowMsg(){cout<<"This is father.";} //virtual必須有
};
class Son :public Father
{
public :
virtual void ShowMsg(){cout<<"This is Son.";} //virtual關鍵字可有可無
};
class Daughter :public Father
{
public :
void ShowMsg(){cout<<"This is Daughter."<<endl;}
};
void ShowYourName(Father* fa)
{
fa->ShowMsg();
}
int _tmain(int argc, _TCHAR* argv[])
{
Father fa;
Son son;
Daughter da;
Father* pFa;
pFa = &fa;
ShowYourName(pFa); //結果this is father
pFa = &son;
ShowYourName(pFa); //結果this is son
pFa = &da;
ShowYourName(pFa); //結果this is daughter
return 0;
}
貌似C++裡面沒有像C#一樣可以用new關鍵字來隱藏子類的函數.不過有個操作可以實作類別似的功能.
假如這樣Father *pFa; Daughter da;
pFa = &((Father)da);
ShowYourName(pFa); //這樣結果就會為this is father,和上面的樣本不一樣,上面樣本是this is daughter.
C++虛函數實現多態的原理
看了樣本後還有點雲裡霧裡的.虛函數怎麼就莫名其妙的實現了多態呢?
這就要說到虛擬函數表(vtable)
我們想下當建立Father,Son,Daughter這三個類時,在記憶體中是怎麼表示的呢.
類Father,在記憶體中這樣表示的呢.你用sizeof(fa)查看下發現結果為8,其中4個位元組是用來裝int型資料,還有4個位元組是裝著一個指標vptr,如果64位上指標應該是8個位元組了啊.
vptr指向一個虛擬函數表,此表中又裝著另外一個指標(*ShowMsg)(),它指向Father::ShowMsg().(這就是真正儲存函數的地方,(如果不是虛函數而是一般的函數也儲存在這個地區,只不過不是通過指標來找).
類Son繼承了Father,所以會把Father記憶體中的東東拷貝過來,所以sizeof一下也是8.其中4位元組儲存int類型.4位元組儲存指標vptr,同樣指向虛擬函數表,但這裡會做這樣一個操作.如果發現Son中有重寫了虛擬函數,就會更新此表,否則不更新.由於Son重寫了虛擬函數,所以表中的指標(*ShowMsg)()指向的內容變了,指向了Son::ShowMsg()
而類Daughter跟Son的操作完全一樣.
這樣當pFa = &da;表示指標指向類Daughter的記憶體,由於虛函數列表中對應的函數是Daughter::ShowMsg().所以就明白了為啥pFa->ShowMsg();調用的是類Daughter中的這函數了.但假如Daughter沒有重寫虛函數,整個類Daughter就為空白,只是繼承Father,這樣的話它的虛函數表中所指的函數就是Father::ShowMsg()了
現在虛函數是怎麼實現多態的是弄明白了,那剛才上面說的那個與C#中有new關鍵字隱藏子類函數的操作是咋回事呢?
Father *pFa; Daughter da;
pFa = &((Father)da); //注意這個地方(Father)da 進行類型轉換時實際上是完全拷貝了類Father記憶體中的內容.所以虛函數列表指向的函數就是Father::ShowMsg()了
ShowYourName(pFa);