C++之多態(tài)(內(nèi)容不錯)
編譯環(huán)境:WIN10 VS2017
這篇博客有點長,但都是滿滿的干貨,一定要看到最后,那才是重點。
什么是多態(tài)?
顧名思義就是同一個事物在不同場景下的多種形態(tài)。
下面會具體的詳細(xì)的介紹。
靜態(tài)多態(tài)
我們以前說過的函數(shù)重載就是一個簡單的靜態(tài)多態(tài)
int Add(int left, int right)
{
return left + right;
}
double Add(double left, int right)
{
return left + right;
}
int main()
{
Add(10, 20);
//Add(10.0, 20.0); //這是一個問題代碼
Add(10.0,20); //正常代碼
return 0;
}
可以看出來,靜態(tài)多態(tài)是編譯器在編譯期間完成的,編譯器會根據(jù)實參類型來選擇調(diào)用合適的函數(shù),如果有合適的函數(shù)可以調(diào)用就調(diào),沒有的話就會發(fā)出警告或者報錯。。。比較簡單,不做多介紹。

動態(tài)多態(tài)
什么是動態(tài)多態(tài)呢?
動態(tài)多態(tài): 顯然這和靜態(tài)多態(tài)是一組反義詞,它是在程序運行時根據(jù)基類的引用(指針)指向的對象來確定自己具體該調(diào)用哪一個類的虛函數(shù)。
我在西安臨潼上學(xué),我就以這邊的公交車舉個栗子?。?/p>
class TakeBus
{
public:
void TakeBusToSubway()
{
cout << "go to Subway--->please take bus of 318" << endl;
}
void TakeBusToStation()
{
cout << "go to Station--->pelase Take Bus of 306 or 915" << endl;
}
};
//知道了去哪要做什么車可不行,我們還得知道有沒有這個車
class Bus
{
public:
virtual void TakeBusToSomewhere(TakeBus& tb) = 0; //???為什么要等于0
};
class Subway:public Bus
{
public:
virtual void TakeBusToSomewhere(TakeBus& tb)
{
tb.TakeBusToSubway();
}
};
class Station :public Bus
{
public:
virtual void TakeBusToSomewhere(TakeBus& tb)
{
tb.TakeBusToStation();
}
};
int main()
{
TakeBus tb;
Bus* b = NULL;
//假設(shè)有十輛公交車,如果是奇數(shù)就是去地鐵口的,反之就是去火車站的
for (int i = 1; i <= 10; ++i)
{
if ((rand() % i) & 1)
b = new Subway;
else
b = new Station;
}
b->TakeBusToSomewhere(tb);
delete b;
return 0;
}
這就是一個簡單的動態(tài)多態(tài)的例子,它是在程序運行時根據(jù)條件去選擇調(diào)用哪一個函數(shù)。
而且,從上面的例子我們還發(fā)現(xiàn)了我在每一個函數(shù)前都加了virtual這個虛擬關(guān)鍵字,想想為什么?如果不加會不會構(gòu)成多態(tài)呢?
干想不如上機實踐:
class Base
{
public:
virtual void Funtest1(int i)
{
cout << "Base::Funtest1()" << endl;
}
void Funtest2(int i)
{
cout << "Base::Funtest2()" << endl;
}
};
class Drived :public Base
{
virtual void Funtest1(int i)
{
cout << "Drived::Fubtest1()" << endl;
}
virtual void Funtest2(int i)
{
cout << "Drived::Fubtest2()" << endl;
}
void Funtest2(int i)
{
cout << "Drived::Fubtest2()" << endl;
}
};
void TestVirtual(Base& b)
{
b.Funtest1(1);
b.Funtest2(2);
}
int main()
{
Base b;
Drived d;
TestVirtual(b);
TestVirtual(d);
return 0;
}
在調(diào)用FuncTest2的時候我們看出來他并沒有給我們調(diào)用派生類的函數(shù),因此我們可以對動態(tài)多態(tài)的實現(xiàn)做個總結(jié)。
動態(tài)多態(tài)的條件:
●基類中必須包含虛函數(shù),并且派生類中一定要對基類中的虛函數(shù)進(jìn)行重寫。
●通過基類對象的指針或者引用調(diào)用虛函數(shù)。
重寫 :
(a)基類中將被重寫的函數(shù)必須為虛函數(shù)(上面的檢測用例已經(jīng)證實過了)
(b)基類和派生類中虛函數(shù)的原型必須保持一致(返回值類型,函數(shù)名稱以及參數(shù)列表),協(xié)變和析構(gòu)函數(shù)(基類和派生類的析構(gòu)函數(shù)是不一樣的)除外
(c)訪問限定符可以不同
那么問題又來了,什么是協(xié)變?
協(xié)變:基類(或者派生類)的虛函數(shù)返回基類(派生類)的指針(引用)
總結(jié)一道面試題:那些函數(shù)不能定義為虛函數(shù)?
經(jīng)檢驗下面的幾個函數(shù)都不能定義為虛函數(shù):
1)友元函數(shù),它不是類的成員函數(shù)
2)全局函數(shù)
3)靜態(tài)成員函數(shù),它沒有this指針
3)構(gòu)造函數(shù),拷貝構(gòu)造函數(shù),以及賦值運算符重載(可以但是一般不建議作為虛函數(shù))
抽象類:
在前面公交車的例子上我提了一個問題:
class Bus
{
public:
virtual void TakeBusToSomewhere(TakeBus& tb) = 0; //???為什么要等于0
};
在成員函數(shù)(必須為虛函數(shù))的形參列表后面寫上=0,則成員函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。純虛函數(shù)在派生類中重新定義以后,派生類才能實例化出對象。純虛函數(shù)是一定要被繼承的,否則它存在沒有任何意義。

多態(tài)調(diào)用原理
class Base
{
public:
virtual void Funtest1(int i)
{
cout << "Base::Funtest1()" << endl;
}
virtual void Funtest2(int i)
{
cout << "Base::Funtest2()" << endl;
}
int _data;
};
int main()
{
cout << sizeof(Base) << endl;
Base b;
b._data = 10;
return 0;
}
8?不知道大家有沒有問題,反正我是有疑惑了。以前在對象模型(https://blog.csdn.net/qq_39412582/article/details/80808754)時我提到過怎么來求一個類的大小。按照那個方法,這里應(yīng)該是4才對啊,為什么會是8呢?
通過觀察。我們發(fā)現(xiàn)這個例子里面和以前不一樣,類成員函數(shù)變成了虛函數(shù),這是不是引起類大小變化的原因呢?
我們假設(shè)就是這樣,然后看看內(nèi)存里是怎么存儲的呢?
可以看到它在內(nèi)存里多了四個字節(jié),那這四個字節(jié)的內(nèi)容到底是什么呢?

是不是有點看不懂,我們假設(shè)它是一個地址去看地址里存的東西的時候發(fā)現(xiàn)它存的是兩個地址。
我假設(shè)它是虛函數(shù)的地址,我們來驗證一下:
typedef void (__stdcall *PVFT)(); //函數(shù)指針
int main()
{
cout << sizeof(Base) << endl;
Base b;
b._data = 10;
PVFT* pVFT = (PVFT*)(*((int*)&b));
while (*pVFT)
{
(*pVFT)();
pVFT+=1;
}
return 0;
}
結(jié)果好像和我們的猜想一樣,是一件開心的事。然后我給一張圖總結(jié)一下:
在反匯編中我們還可以看到,如果含有虛函數(shù)的類中沒有定義構(gòu)造函數(shù),編譯器會自動合成一個構(gòu)造函數(shù)


對于派生類的東西我給個鏈接仔細(xì)看,人家總結(jié)的超級贊,我偷個懶就不寫了,老鐵們包容下啊。
派生類虛表:
1.先將基類的虛表中的內(nèi)容拷貝一份
2.如果派生類對基類中的虛函數(shù)進(jìn)行重寫,使用派生類的虛函數(shù)替換相同偏移量位置的基類虛函數(shù)
3.如果派生類中新增加自己的虛函數(shù),按照其在派生類中的聲明次序,放在上述虛函數(shù)之后
https://coolshell.cn/articles/12176.html
多態(tài)缺陷
●降低了程序運行效率(多態(tài)需要去找虛表的地址)
●空間浪費
- C++中的封裝、繼承、多態(tài)理解
- 詳解C++ 多態(tài)的實現(xiàn)及原理
- c++語言中虛函數(shù)實現(xiàn)多態(tài)的原理詳解
- C++中繼承與多態(tài)的基礎(chǔ)虛函數(shù)類詳解
- C++面向?qū)ο笾鄳B(tài)的實現(xiàn)和應(yīng)用詳解
- C語言實現(xiàn)C++繼承和多態(tài)的代碼分享
- C語言模式實現(xiàn)C++繼承和多態(tài)的實例代碼
- C++多繼承多態(tài)的實例詳解
- C++ 通過指針實現(xiàn)多態(tài)實例詳解
- C++中的多態(tài)與虛函數(shù)的內(nèi)部實現(xiàn)方法
- C++多態(tài)的實現(xiàn)機制深入理解
- 詳解C++編程的多態(tài)性概念
- C++多態(tài)的實現(xiàn)及原理詳細(xì)解析
- 深入解析C++中的虛函數(shù)與多態(tài)
- 深入理解C++的多態(tài)性
相關(guān)文章
解析C++編程中的選擇結(jié)構(gòu)和switch語句的用法
這篇文章主要介紹了解析C++編程中的選擇結(jié)構(gòu)和switch語句的用法,是C++入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2015-09-09
用c++實現(xiàn)將文本每個單詞首字母轉(zhuǎn)換為大寫
本篇文章是對用c++實現(xiàn)將文本每個單詞首字母轉(zhuǎn)換為大寫的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Qt實現(xiàn)網(wǎng)絡(luò)聊天室的示例代碼
本文主要介紹了Qt實現(xiàn)網(wǎng)絡(luò)聊天室,實現(xiàn)一個在線聊天室, 使用tcp對客戶端和服務(wù)器端進(jìn)行通訊。具有一定的參考價值,具有一定的參考價值,2021-06-06
C語言?使用qsort函數(shù)來進(jìn)行快速排序
排序方法有很多種:選擇排序,冒泡排序,歸并排序,快速排序等。?看名字都知道快速排序是目前公認(rèn)的一種比較好的排序算法。因為他速度很快,所以系統(tǒng)也在庫里實現(xiàn)這個算法,便于我們的使用。?這就是qsort函數(shù)2022-02-02

