網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
C++多態(tài)是在繼承的基礎(chǔ)上實(shí)現(xiàn)的,了解多態(tài)之前我們需要掌握一定的C++繼承的知識(shí),本文將介紹C++中多態(tài)的概念,構(gòu)成條件以及用法。
1.多態(tài)的概念
多態(tài),通俗來(lái)講就是多種形態(tài),具體點(diǎn)就是去完成某個(gè)行為,當(dāng)不同的對(duì)象去完成時(shí)會(huì)產(chǎn)生出不同的狀態(tài)。比如,在買票這一行為,普通人買票是全價(jià)買票,學(xué)生買票是半價(jià)買票,而軍人買票是優(yōu)先買票;再比如動(dòng)物園的動(dòng)物叫這個(gè)行為,不同的動(dòng)物叫聲是不一樣的。這些都是生活中多態(tài)的例子。
2.C++中多態(tài)的分類
(1)靜態(tài)多態(tài)
靜態(tài)多態(tài)是指在編譯時(shí)實(shí)現(xiàn)的多態(tài),比如函數(shù)重載,看似是調(diào)用同一個(gè)函數(shù)其實(shí)是在調(diào)用不同的。
比如我們使用cout這個(gè)對(duì)象來(lái)調(diào)用<<時(shí):
int i=1;
double d=2.2;
cout<<i<<endl;
cout<<d<<endl;
雖然調(diào)用的都是<<,但其實(shí)調(diào)用的是不同的操作符重載之后的函數(shù)。
函數(shù)重載在之前的文章中詳細(xì)講解過,這里就不再贅述。
(2)動(dòng)態(tài)多態(tài)
動(dòng)態(tài)多態(tài)也就是我們通常所說的多態(tài),本文以下內(nèi)容均為動(dòng)態(tài)多態(tài)內(nèi)容。動(dòng)態(tài)多態(tài)是在運(yùn)行中實(shí)現(xiàn)的,
當(dāng)一個(gè)父類對(duì)象的引用或者指針接收不同的對(duì)象(父類對(duì)象or子類對(duì)象)后,調(diào)用相同的函數(shù)會(huì)調(diào)用不同的函數(shù)。
這段話也許比較繞,這里只是給出一個(gè)概念,可以結(jié)合下面的例子來(lái)進(jìn)行理解。
3.多態(tài)的構(gòu)成條件
(1)舉例
我們先根據(jù)一個(gè)構(gòu)成例子來(lái)理解多態(tài)構(gòu)成的條件:
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "全價(jià)買票" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTicket()
{
cout << "半價(jià)買票" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p1;
Student p2;
Func(p1);
Func(p2);
}
我們先來(lái)看這樣一段代碼,其中子類Student繼承了父類。運(yùn)行起來(lái)打印的結(jié)果是:
我們?cè)诜从^上述中動(dòng)態(tài)多態(tài)的定義,用父類的引用或者指針(這里使用的是Person& p)來(lái)接收不同類型的對(duì)象(p1和p2),該引用或指針調(diào)用相同的函數(shù)(都調(diào)用了p.BuyTicket()),都調(diào)用了各自類中不同的函數(shù)(打印的結(jié)果不同)。我們將這一過程稱為動(dòng)態(tài)多態(tài)。
如果我們不傳指針或者引用,那么將不構(gòu)成多態(tài)(原理會(huì)在多態(tài)原理中詳細(xì)解讀)。
(2)兩個(gè)概念
在解釋多態(tài)的構(gòu)成條件之前我們還需要了解兩個(gè)概念。
虛函數(shù)
虛函數(shù),即被virtual修飾的類成員函數(shù)稱為虛函數(shù)。
比如上面代碼中父類和子類的成員函數(shù)就是虛函數(shù)。
virtual void BuyTicket()
{
cout << "全價(jià)買票" << endl;
}
關(guān)于虛函數(shù)還需要注意幾點(diǎn):
1.普通的函數(shù)不能是虛函數(shù),只能是類中的函數(shù)。
2.靜態(tài)成員函數(shù)不能加virtual
總結(jié)起來(lái)就是只能是類的非靜態(tài)成員函數(shù)才能去形成虛函數(shù)。
虛函數(shù)的重寫
虛函數(shù)的重寫又稱為虛函數(shù)的覆蓋(重寫是表面意義上的,覆蓋是指原理上的):派生類中有一個(gè)根基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值類型,函數(shù)名,參數(shù)列表完全相同),稱子類的虛函數(shù)重寫了父類的虛函數(shù)。
//父類中的虛函數(shù)
virtual void BuyTicket()
{
cout << "全價(jià)買票" << endl;
}
//子類中的虛函數(shù)
virtual void BuyTicket()
{
cout << "半價(jià)買票" << endl;
}
兩個(gè)虛函數(shù)滿足返回值類型相同,函數(shù)名相同,參數(shù)列表相同。因此子類的虛函數(shù)重寫了父類的虛函數(shù)。
注意,只有虛函數(shù)才能構(gòu)成重寫。
(3)多態(tài)的構(gòu)成條件
多態(tài)的構(gòu)成滿足兩個(gè)條件:
1.必須通過基類的指針或者引用調(diào)用虛函數(shù)。
2.被調(diào)用的虛函數(shù)的派生類必須完成了對(duì)基類虛函數(shù)的重寫。
我們?cè)趤?lái)看上面的代碼,確實(shí)滿足該條件:
1.使用了父類引用p來(lái)調(diào)用虛函數(shù)。
2.派生類的虛函數(shù)完成了對(duì)基類的虛函數(shù)的重寫。
我們首先要明確使用多態(tài)的目的,就是使用不同的對(duì)象去完成同一個(gè)任務(wù)的時(shí)候會(huì)產(chǎn)生不同的結(jié)果。
如果我們拿掉以上任何一個(gè)條件都不會(huì)再構(gòu)成多態(tài),比如我們不使用指針或者引用去接收對(duì)象從而調(diào)用虛函數(shù),而是使用對(duì)象呢?
void Func(Person p)
{
p.BuyTicket();
}
此時(shí)我們會(huì)發(fā)現(xiàn),打印的結(jié)果發(fā)生了變化:
這是不滿足我們的預(yù)期的,因?yàn)椴煌膶?duì)象傳給了p,p調(diào)用相同的函數(shù)卻打印了相同的結(jié)果。
我們還可以將更改參數(shù)列表或者將父類的virtual拿掉,發(fā)現(xiàn)依然不是我們想要的結(jié)果。
但是有兩個(gè)特殊的情況除外:
4.虛函數(shù)重寫的兩個(gè)例外
(1)協(xié)變
如果我們將父類和子類中的虛函數(shù)的返回值設(shè)為不同,可能會(huì)發(fā)生如下報(bào)錯(cuò):
協(xié)變指的是:派生類重寫基類虛函數(shù)時(shí),與基類虛函數(shù)返回值類型不同。即基類的虛函數(shù)返回基類對(duì)象的指針或者引用,派生類虛函數(shù)返回派生類對(duì)象的指針或者引用時(shí),稱為協(xié)變。
簡(jiǎn)單來(lái)說就是兩者的返回值是父子關(guān)系的指針或者引用。
舉一個(gè)例子:
class A{};
class B:public A
{};
class Person
{
public:
virtual A* BuyTicket()
{
A a;
cout << "全價(jià)買票" << endl;
return &a;
}
};
class Student :public Person
{
public:
virtual B* BuyTicket()
{
B b;
cout << "半價(jià)買票" << endl;
return &b;
}
};
我們將上一段代碼進(jìn)行了改寫,定義了B繼承A,而在Person和Student兩個(gè)類中的虛函數(shù)中將返回值分別置為A和B,由于A和B是繼承關(guān)系,所以仍然可以構(gòu)成多態(tài),我們稱派生類的虛函數(shù)為基類的虛函數(shù)的協(xié)變。
注意返回值必須是指針或者引用,對(duì)象不會(huì)構(gòu)成協(xié)變。
(2)析構(gòu)函數(shù)的重寫
首先我們先回顧一下沒有構(gòu)成多態(tài)的析構(gòu)函數(shù)調(diào)用:只需要子類對(duì)象銷毀時(shí)無(wú)需手動(dòng)銷毀父類對(duì)象,會(huì)自動(dòng)調(diào)用父類對(duì)象的析構(gòu)函數(shù)。
1.如果基類的析構(gòu)函數(shù)為虛函數(shù),此時(shí)子類的析構(gòu)函數(shù)無(wú)論加不加virtual,都是對(duì)父類的析構(gòu)函數(shù)的重寫。
2.雖然子類和父類的析構(gòu)函數(shù)的函數(shù)名不同,但其實(shí)編譯器對(duì)析構(gòu)函數(shù)的名稱進(jìn)行了特殊的處理,都處理成了destructor。
下面舉例說明,將Person和Student寫入析構(gòu)函數(shù):
//父類中的析構(gòu)函數(shù)
virtual ~Person()
{
cout << "~Person" << endl;
}
//子類中的析構(gòu)函數(shù)
virtual ~Student()
{
cout << " ~Student" << endl;
}
//主函數(shù)
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
構(gòu)成多態(tài)的結(jié)果是,Person*類型的p1和p2,接收兩個(gè)不同類型的對(duì)象即Person類型和Student類型,在調(diào)用析構(gòu)函數(shù)的時(shí)候可以分開調(diào)用(子類對(duì)象調(diào)用子類的析構(gòu)函數(shù),父類對(duì)象調(diào)用父類的析構(gòu)函數(shù)。)
我們將上述代碼運(yùn)行一下,會(huì)發(fā)現(xiàn):
結(jié)果的確是如此,當(dāng)析構(gòu)父類對(duì)象時(shí),調(diào)用父類的析構(gòu)函數(shù),當(dāng)析構(gòu)子類對(duì)象時(shí),調(diào)用的是子類的析構(gòu)函數(shù)和父類的析構(gòu)函數(shù)。
如果我們不使用父類指針進(jìn)行管理,而是使用對(duì)象來(lái)接收子類對(duì)象呢?
Student p2;
Person p3 = p2;
此時(shí)我們發(fā)現(xiàn)打印的結(jié)果是:
在析構(gòu)p3的時(shí)候,并沒有根據(jù)按Student類的規(guī)則來(lái)進(jìn)行析構(gòu)。
同時(shí),當(dāng)我們將派生類的virtual去掉的時(shí)候,仍然可以構(gòu)成多態(tài),這與底層原理有關(guān),在下面的介紹中會(huì)提及。為了統(tǒng)一性,不建議將virtual拿掉,C++大佬為了防止發(fā)生不必要的內(nèi)存泄漏,所以設(shè)置了這一規(guī)則。這就導(dǎo)致所有的其實(shí)派生類的所有虛函數(shù)virtual都可以省略。這是由于其繼承了基類的virtual屬性,具體的還要在底層去理解,再?gòu)?qiáng)調(diào)一遍,盡量不要在派生類中省略virtual。
5.final與override
(1)final
限制類不被繼承
但我們想要設(shè)計(jì)一個(gè)不被繼承的類時(shí),目前我們知道的有一種方法:就是將父類的構(gòu)造函數(shù)設(shè)為私有(這是因?yàn)樽宇愋枰{(diào)用父類的構(gòu)造函數(shù)來(lái)進(jìn)行初始化)。如果使用這種方式,定義父類對(duì)象的話需要使用單例模式。
final提供了另一種方式來(lái)限制一個(gè)類不會(huì)被繼承。
只需要在不想被繼承的類后加final即可:
class Person final
{
public:
virtual A* BuyTicket()
{
A a;
cout << "全價(jià)買票" << endl;
return &a;
}
virtual ~Person()
{
cout << "~Person" << endl;
}
};
此時(shí)如果子類去繼承Person的話會(huì)報(bào)錯(cuò)。
限制虛函數(shù)不被重寫
當(dāng)我們?cè)诤瘮?shù)后加上final的時(shí)候,該虛函數(shù)將不能被子類中的虛函數(shù)重寫,否則會(huì)發(fā)生報(bào)錯(cuò)。
virtual A* BuyTicket() final
{
A a;
cout << "全價(jià)買票" << endl;
return &a;
}
(2)override
將override放在子類的重寫的虛函數(shù)后,判斷是否完成重寫(重寫的是否正確)
virtual B* BuyTicket(int i=10) override
{
B b;
cout << "半價(jià)買票" << endl;
return &b;
}
注意:final關(guān)鍵字放在父類的位置,override關(guān)鍵字放在子類的位置。
6.抽象類
在虛函數(shù)的后面加上=0,則這個(gè)函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫做接口類),抽象類不能實(shí)例化出對(duì)象。派生類繼承后也不能實(shí)例化出對(duì)象。**只有重寫虛函數(shù),派生類才能實(shí)例化出對(duì)象。**注意雖然不能實(shí)例化出對(duì)象,但是可以定義指針。
抽象類的存在本質(zhì)上來(lái)說就是希望我們?cè)谂缮愔兄貙懜割惖奶摵瘮?shù)。抽象類中的虛函數(shù)一般只聲明,不實(shí)現(xiàn),因?yàn)闆]有意義。我們可以搭配override來(lái)使用。
//將父類中寫入純虛函數(shù),父類變成抽象類
class Person
{
public:
virtual A* BuyTicket() =0//純虛函數(shù)
{
A a;
cout << "全價(jià)買票" << endl;
return &a;
}
virtual ~Person()
{
cout << "~Person" << endl;
}
};
此時(shí)子類必須只有重寫虛函數(shù)才能定義對(duì)象。通常情況下現(xiàn)實(shí)中沒有的事物,定義成抽象類會(huì)比較合適。
雖然我們不能使用抽象類來(lái)定義對(duì)象,但是我們可以使用抽象類來(lái)定義指針。
class Car
{
public:
virtual void Drive() = 0
{
cout << " Car" << endl;
}
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz" << endl;
}
void f()
{
cout << "f()" << endl;
}
};
int main()
{
//Car* p = nullptr;
//p->Drive();//程序會(huì)崩潰
Car* a = new Benz;
a->Drive();
}
我們可以使用父類指針去接收子類對(duì)象,同時(shí)調(diào)用函數(shù)。但是不能使用父類去創(chuàng)建對(duì)象。
7.總結(jié)
C++多態(tài)的目的在于當(dāng)我們使用父類的指針或者引用去接收子類的對(duì)象后,接收不同的子類對(duì)象的父類指針或者引用調(diào)用的相同的函數(shù)產(chǎn)生的結(jié)果不同。
重點(diǎn)在于實(shí)現(xiàn)多態(tài)的幾個(gè)條件:
一是用父類的指針或者引用來(lái)接收。
二是子類必須對(duì)父類的虛函數(shù)進(jìn)行重寫。
原文鏈接:https://blog.csdn.net/qq_51492202/article/details/124325549
相關(guān)推薦
- 2022-11-02 Python運(yùn)維自動(dòng)化之paramiko模塊應(yīng)用實(shí)例_python
- 2023-07-30 vscode自定義用戶代碼片段
- 2022-05-05 Python學(xué)習(xí)之不同數(shù)據(jù)類型間的轉(zhuǎn)換總結(jié)_python
- 2022-12-14 Python開發(fā)之利用re模塊去除代碼塊注釋_python
- 2022-09-23 Go語(yǔ)言fmt.Sprintf格式化輸出的語(yǔ)法與實(shí)例_Golang
- 2023-06-17 Flask中特殊裝飾器的使用_python
- 2022-09-01 基于MFC實(shí)現(xiàn)單個(gè)文檔的文件讀寫_C 語(yǔ)言
- 2022-09-14 python?服務(wù)器批處理得到PSSM矩陣的問題_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支