網站首頁 編程語言 正文
前言
C++多態是在繼承的基礎上實現的,了解多態之前我們需要掌握一定的C++繼承的知識,本文將介紹C++中多態的概念,構成條件以及用法。
1.多態的概念
多態,通俗來講就是多種形態,具體點就是去完成某個行為,當不同的對象去完成時會產生出不同的狀態。比如,在買票這一行為,普通人買票是全價買票,學生買票是半價買票,而軍人買票是優先買票;再比如動物園的動物叫這個行為,不同的動物叫聲是不一樣的。這些都是生活中多態的例子。
2.C++中多態的分類
(1)靜態多態
靜態多態是指在編譯時實現的多態,比如函數重載,看似是調用同一個函數其實是在調用不同的。
比如我們使用cout這個對象來調用<<時:
int i=1;
double d=2.2;
cout<<i<<endl;
cout<<d<<endl;
雖然調用的都是<<,但其實調用的是不同的操作符重載之后的函數。
函數重載在之前的文章中詳細講解過,這里就不再贅述。
(2)動態多態
動態多態也就是我們通常所說的多態,本文以下內容均為動態多態內容。動態多態是在運行中實現的,
當一個父類對象的引用或者指針接收不同的對象(父類對象or子類對象)后,調用相同的函數會調用不同的函數。
這段話也許比較繞,這里只是給出一個概念,可以結合下面的例子來進行理解。
3.多態的構成條件
(1)舉例
我們先根據一個構成例子來理解多態構成的條件:
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "全價買票" << endl;
}
};
class Student :public Person
{
public:
virtual void BuyTicket()
{
cout << "半價買票" << endl;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p1;
Student p2;
Func(p1);
Func(p2);
}
我們先來看這樣一段代碼,其中子類Student繼承了父類。運行起來打印的結果是:
我們在反觀上述中動態多態的定義,用父類的引用或者指針(這里使用的是Person& p)來接收不同類型的對象(p1和p2),該引用或指針調用相同的函數(都調用了p.BuyTicket()),都調用了各自類中不同的函數(打印的結果不同)。我們將這一過程稱為動態多態。
如果我們不傳指針或者引用,那么將不構成多態(原理會在多態原理中詳細解讀)。
(2)兩個概念
在解釋多態的構成條件之前我們還需要了解兩個概念。
虛函數
虛函數,即被virtual修飾的類成員函數稱為虛函數。
比如上面代碼中父類和子類的成員函數就是虛函數。
virtual void BuyTicket()
{
cout << "全價買票" << endl;
}
關于虛函數還需要注意幾點:
1.普通的函數不能是虛函數,只能是類中的函數。
2.靜態成員函數不能加virtual
總結起來就是只能是類的非靜態成員函數才能去形成虛函數。
虛函數的重寫
虛函數的重寫又稱為虛函數的覆蓋(重寫是表面意義上的,覆蓋是指原理上的):派生類中有一個根基類完全相同的虛函數(即派生類虛函數與基類虛函數的返回值類型,函數名,參數列表完全相同),稱子類的虛函數重寫了父類的虛函數。
//父類中的虛函數
virtual void BuyTicket()
{
cout << "全價買票" << endl;
}
//子類中的虛函數
virtual void BuyTicket()
{
cout << "半價買票" << endl;
}
兩個虛函數滿足返回值類型相同,函數名相同,參數列表相同。因此子類的虛函數重寫了父類的虛函數。
注意,只有虛函數才能構成重寫。
(3)多態的構成條件
多態的構成滿足兩個條件:
1.必須通過基類的指針或者引用調用虛函數。
2.被調用的虛函數的派生類必須完成了對基類虛函數的重寫。
我們在來看上面的代碼,確實滿足該條件:
1.使用了父類引用p來調用虛函數。
2.派生類的虛函數完成了對基類的虛函數的重寫。
我們首先要明確使用多態的目的,就是使用不同的對象去完成同一個任務的時候會產生不同的結果。
如果我們拿掉以上任何一個條件都不會再構成多態,比如我們不使用指針或者引用去接收對象從而調用虛函數,而是使用對象呢?
void Func(Person p)
{
p.BuyTicket();
}
此時我們會發現,打印的結果發生了變化:
這是不滿足我們的預期的,因為不同的對象傳給了p,p調用相同的函數卻打印了相同的結果。
我們還可以將更改參數列表或者將父類的virtual拿掉,發現依然不是我們想要的結果。
但是有兩個特殊的情況除外:
4.虛函數重寫的兩個例外
(1)協變
如果我們將父類和子類中的虛函數的返回值設為不同,可能會發生如下報錯:
協變指的是:派生類重寫基類虛函數時,與基類虛函數返回值類型不同。即基類的虛函數返回基類對象的指針或者引用,派生類虛函數返回派生類對象的指針或者引用時,稱為協變。
簡單來說就是兩者的返回值是父子關系的指針或者引用。
舉一個例子:
class A{};
class B:public A
{};
class Person
{
public:
virtual A* BuyTicket()
{
A a;
cout << "全價買票" << endl;
return &a;
}
};
class Student :public Person
{
public:
virtual B* BuyTicket()
{
B b;
cout << "半價買票" << endl;
return &b;
}
};
我們將上一段代碼進行了改寫,定義了B繼承A,而在Person和Student兩個類中的虛函數中將返回值分別置為A和B,由于A和B是繼承關系,所以仍然可以構成多態,我們稱派生類的虛函數為基類的虛函數的協變。
注意返回值必須是指針或者引用,對象不會構成協變。
(2)析構函數的重寫
首先我們先回顧一下沒有構成多態的析構函數調用:只需要子類對象銷毀時無需手動銷毀父類對象,會自動調用父類對象的析構函數。
1.如果基類的析構函數為虛函數,此時子類的析構函數無論加不加virtual,都是對父類的析構函數的重寫。
2.雖然子類和父類的析構函數的函數名不同,但其實編譯器對析構函數的名稱進行了特殊的處理,都處理成了destructor。
下面舉例說明,將Person和Student寫入析構函數:
//父類中的析構函數
virtual ~Person()
{
cout << "~Person" << endl;
}
//子類中的析構函數
virtual ~Student()
{
cout << " ~Student" << endl;
}
//主函數
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
構成多態的結果是,Person*類型的p1和p2,接收兩個不同類型的對象即Person類型和Student類型,在調用析構函數的時候可以分開調用(子類對象調用子類的析構函數,父類對象調用父類的析構函數。)
我們將上述代碼運行一下,會發現:
結果的確是如此,當析構父類對象時,調用父類的析構函數,當析構子類對象時,調用的是子類的析構函數和父類的析構函數。
如果我們不使用父類指針進行管理,而是使用對象來接收子類對象呢?
Student p2;
Person p3 = p2;
此時我們發現打印的結果是:
在析構p3的時候,并沒有根據按Student類的規則來進行析構。
同時,當我們將派生類的virtual去掉的時候,仍然可以構成多態,這與底層原理有關,在下面的介紹中會提及。為了統一性,不建議將virtual拿掉,C++大佬為了防止發生不必要的內存泄漏,所以設置了這一規則。這就導致所有的其實派生類的所有虛函數virtual都可以省略。這是由于其繼承了基類的virtual屬性,具體的還要在底層去理解,再強調一遍,盡量不要在派生類中省略virtual。
5.final與override
(1)final
限制類不被繼承
但我們想要設計一個不被繼承的類時,目前我們知道的有一種方法:就是將父類的構造函數設為私有(這是因為子類需要調用父類的構造函數來進行初始化)。如果使用這種方式,定義父類對象的話需要使用單例模式。
final提供了另一種方式來限制一個類不會被繼承。
只需要在不想被繼承的類后加final即可:
class Person final
{
public:
virtual A* BuyTicket()
{
A a;
cout << "全價買票" << endl;
return &a;
}
virtual ~Person()
{
cout << "~Person" << endl;
}
};
此時如果子類去繼承Person的話會報錯。
限制虛函數不被重寫
當我們在函數后加上final的時候,該虛函數將不能被子類中的虛函數重寫,否則會發生報錯。
virtual A* BuyTicket() final
{
A a;
cout << "全價買票" << endl;
return &a;
}
(2)override
將override放在子類的重寫的虛函數后,判斷是否完成重寫(重寫的是否正確)
virtual B* BuyTicket(int i=10) override
{
B b;
cout << "半價買票" << endl;
return &b;
}
注意:final關鍵字放在父類的位置,override關鍵字放在子類的位置。
6.抽象類
在虛函數的后面加上=0,則這個函數為純虛函數。包含純虛函數的類叫做抽象類(也叫做接口類),抽象類不能實例化出對象。派生類繼承后也不能實例化出對象。**只有重寫虛函數,派生類才能實例化出對象。**注意雖然不能實例化出對象,但是可以定義指針。
抽象類的存在本質上來說就是希望我們在派生類中重寫父類的虛函數。抽象類中的虛函數一般只聲明,不實現,因為沒有意義。我們可以搭配override來使用。
//將父類中寫入純虛函數,父類變成抽象類
class Person
{
public:
virtual A* BuyTicket() =0//純虛函數
{
A a;
cout << "全價買票" << endl;
return &a;
}
virtual ~Person()
{
cout << "~Person" << endl;
}
};
此時子類必須只有重寫虛函數才能定義對象。通常情況下現實中沒有的事物,定義成抽象類會比較合適。
雖然我們不能使用抽象類來定義對象,但是我們可以使用抽象類來定義指針。
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();//程序會崩潰
Car* a = new Benz;
a->Drive();
}
我們可以使用父類指針去接收子類對象,同時調用函數。但是不能使用父類去創建對象。
7.總結
C++多態的目的在于當我們使用父類的指針或者引用去接收子類的對象后,接收不同的子類對象的父類指針或者引用調用的相同的函數產生的結果不同。
重點在于實現多態的幾個條件:
一是用父類的指針或者引用來接收。
二是子類必須對父類的虛函數進行重寫。
原文鏈接:https://blog.csdn.net/qq_51492202/article/details/124325549
相關推薦
- 2022-06-02 TensorFlow實現簡單線性回歸_python
- 2022-04-17 Jquery cxSelect多級聯動下拉組件的使用
- 2022-08-27 golang?隨機數的兩種方式_Golang
- 2021-12-07 Android證書安裝過程介紹_Android
- 2022-08-13 electron功能實現---添加全局快捷鍵、開機自啟、選擇安裝路徑
- 2022-07-06 C++詳細分析講解引用的概念與使用_C 語言
- 2022-07-14 python中序列的逆序方式_python
- 2022-09-09 python處理xml文件操作詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支