初识C++之多态
多態(tài)性是將接口與實現(xiàn)進行分離;用形象的語言來解釋就是實現(xiàn)以共同的方法,但因個體差異,而采用不同的策略。
1、什么是多態(tài)
多態(tài)(Polymorphism)按字面的意思就是“多種狀態(tài)”。在面向?qū)ο笳Z言中,接口的多種不同的實現(xiàn)方式即為多態(tài)。它是面向?qū)ο蟪绦蛟O(shè)計(OOP)的一個重要特征。如果一個語言只支持類而不支持多態(tài),只能說明它是基于對象的,而不是面向?qū)ο蟮摹++中的多態(tài)性具體體現(xiàn)在運行和編譯兩個方面。它可以簡單地概括為“一個接口,多種方法”。
那么多態(tài)的作用是什么呢,封裝可以使得代碼模塊化,繼承可以擴展已存在的代碼,他們的目的都是為了代碼重用。而多態(tài)的目的則是為了接口重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數(shù)都能夠通過同一個接口調(diào)用到適應(yīng)各自對象的實現(xiàn)方法。
輸出:
可以看到,雖然pa1、pa1都是基類的指針,但是調(diào)用時卻調(diào)用的是Person、Horse兩個派生類的方法。這是因為pa1和pa2分別指向了Person、Horse兩個派生類的對象。原來下來慢慢剖析。
2、多態(tài)的分類
①編譯時多態(tài):也叫靜態(tài)多態(tài),在編譯時就可以確定對象使用的形式。 實現(xiàn)機制使通過函數(shù)重載(運算符重載實質(zhì)也屬于函數(shù)重載)。
②運行時多態(tài):也叫動態(tài)多態(tài),其具體引用的對象在運行時才能確定。 實現(xiàn)機制是通過虛函數(shù)。
靜態(tài)多態(tài)性與動態(tài)多態(tài)性的實質(zhì)區(qū)別就是函數(shù)地址是早綁定還是晚綁定。如果函數(shù)的調(diào)用,在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并生產(chǎn)代碼,是靜態(tài)的,就是說地址是早綁定的。而如果函數(shù)調(diào)用的地址不能在編譯器期間確定,需要在運行時才確定,這就屬于晚綁定。
3、動態(tài)多態(tài)性存在的三個必要條件
①要有類的繼承關(guān)系;
②要有虛函數(shù)(只有重寫了虛函數(shù)的才能算作是體現(xiàn)了C++多態(tài)性);
注意函數(shù)重寫、重載、重定義的區(qū)別,可以參考:
http://blog.csdn.net/ljx_5489464/article/details/51127081
③要有父類指針/引用指向子類對象(用于訪問派生類中同名覆蓋成員函數(shù))。
4、多態(tài)的實現(xiàn)
C++多態(tài)性是通過虛函數(shù)來實現(xiàn)的,通過指向派生類的基類指針或引用,訪問派生類中同名覆蓋成員函數(shù)。虛函數(shù)允許子類重新定義成員函數(shù),而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫(重寫的話可以有兩種,直接重寫成員函數(shù)和重寫虛函數(shù),只有重寫了虛函數(shù)的才能算作是體現(xiàn)了C++多態(tài)性)。
虛函數(shù):在某基類中聲明為 virtual 并在一個或多個派生類中被重新定 義的成員函數(shù),用法格式為:
virtual 函數(shù)返回類型 函數(shù)名(參數(shù)表)
{
函數(shù)體;
}
關(guān)于虛函數(shù)這里不做過剖析,可以參考:
http://blog.csdn.net/ljx_5489464/article/details/51138393
前面說了,多態(tài)的動態(tài)性實現(xiàn)是靠虛函數(shù),它的實質(zhì)是在基類定義一個虛函數(shù),在派生類中對其進行重寫,然后定義基類的指針指向派生類對象,然后用該指針調(diào)用虛函數(shù),此時調(diào)用的就是指針指向的對象對應(yīng)的類里面的同名函數(shù)。那么這種機制在底層是怎樣實現(xiàn)的呢?
想要弄清楚這個就必須要清楚一個東西–>虛表,全稱為虛擬函數(shù)表。什么是虛表,虛表就是一張表,它里面存放虛函數(shù)的入口地址,在C++語言中,每個有虛函數(shù)的類或者基類有虛函數(shù)的派生類,編譯器都會為它生成一個虛擬函數(shù)表(注意:虛表是從屬于類的)。此外,編譯器會為包含虛函數(shù)的類加上一個成員變量,是一個指向該虛函數(shù)表的指針(常被稱為vfptr,注意:虛表指針是從屬于對象的)。也就是說,如果一個類含有虛表,則該類的所有對象都會含有一個虛表指針,并且該虛表指針指向同一個虛表。
虛表的內(nèi)容是依據(jù)類中的虛函數(shù)聲明次序–填入函數(shù)指針。派生類會繼承基礎(chǔ)類別的虛表(以及所有其他可以繼承的成員),再在此虛表上添加上自己的虛函數(shù)指針,當(dāng)我們在派生類中改寫虛函數(shù)時,虛表就受了影響,改寫后的的虛函數(shù)的地址會替換原虛函數(shù)的地址存放于虛表中。
①no_Virtual類沒有任何數(shù)據(jù)成員,也沒有虛函數(shù),按說它的大小應(yīng)該是0,但這兒它的大小是1。我理解的原因有以下兩點:
這個是因為no_Virtual既然是一個類型,那么就能用來定義變量,而定義變量肯定要分配空間,分配多大合適呢?0個?顯然不可能,2個、4個?太浪費了,它什么都不用存儲,分配那么大干什么,所以編譯器就為我們選了個折中的辦法,分配一個,既不會浪費空間,也能用來定義變量。
既然no_Virtual是類型,用來定義變量的,那么要是它一個空間都不占的話,用它定義出來的變量就都從同一位置開始存儲了,那我們訪問那塊空間時,怎么知道到底是訪問哪一個變量呢?
②Virtual類沒有任何數(shù)據(jù)成員,但是有虛函數(shù),按我們上面的說法,它的大小是4,結(jié)果很給面子的正確了。
定義一個Virtual類的變量,看看是不是像上面說的那樣,有一個從屬于它的虛表指針,從監(jiān)視窗口看,確實是的。
輸出結(jié)果:
因為VS的監(jiān)視窗口好像有Bug:
像這兒,派生類的對象就只能看到繼承與基類的虛表,而不能看到派生類自己的,所以上面我一個函數(shù)分別打印了基類與派生類的虛表,可以很直觀的看到,首先,派生類的虛表是繼承于基類的,然后在加上自己的虛函數(shù)的地址到虛表中去,就構(gòu)成了自己的虛表。
現(xiàn)在來剖析一下打印虛表的函數(shù)是怎么實現(xiàn)的:
首先得清楚帶有虛函數(shù)的類創(chuàng)建的對象,前四個字節(jié)里面存儲的就是虛表指針的地址,如下圖:
再回過頭來看看虛表的打印函數(shù):
void PrintVirTable() {Base b;Derived d;cout << "Base:" << endl;int *vfpTable = (int *)(*(int*)&b);while (*vfpTable != NULL){int *pFun = (int*)(*vfpTable);cout << pFun << endl;vfpTable++;}cout << "Derived:" << endl;vfpTable = (int *)(*(int*)&d);while (*vfpTable != NULL){int *pFun = (int*)(*vfpTable);cout << pFun << endl;vfpTable++;} }它的原理其實是這樣的:
再來看一個例子:
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std;class Base { public:virtual void Fun1(){cout << "Base::Fun1()" << endl;}virtual void Fun2() //注意這兒加了關(guān)鍵字virtual{cout << "Base::Fun2()" << endl;} };class Derived: public Base { public:virtual void Fun3() //注意這兒加了關(guān)鍵字virtual{cout << "Derived::Fun3()" << endl;}virtual void Fun4() //注意這兒加了關(guān)鍵字virtual{cout << "Derived::Fun4()" << endl;} public:int data; };void PrintVirTable() {Derived d1;Derived d2;cout << "d1::virTable" << endl;int *vfpTable = (int *)(*(int*)&d1);while (*vfpTable != NULL){int *pFun = (int*)(*vfpTable);cout << pFun << endl;vfpTable++;}cout << "d2::virTable" << endl;vfpTable = (int *)(*(int*)&d2);while (*vfpTable != NULL){int *pFun = (int*)(*vfpTable);cout << pFun << endl;vfpTable++;} }int main() {PrintVirTable();return 0; }輸出結(jié)果:
這兒就證明了如果一個類有虛函數(shù),那么該不同對象公用同一張?zhí)摫怼R驗楹瘮?shù)的地址是在編譯期間的確定的,一個函數(shù)的地址只會被存儲一次,雖然累的對象不同,但它們調(diào)用該類的同名函數(shù)其實是調(diào)用的同一函數(shù),因此它們的虛表會相同。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
- 上一篇: java圆形泳池问题_Java实现 Le
- 下一篇: c++ 字符串相等比较