網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
前言
最近為了完成數(shù)據(jù)庫(kù)系統(tǒng)的實(shí)驗(yàn),又復(fù)習(xí)起了《C++ Primer》,上一次看這本巨著也是大二下的六月份,那時(shí)看面向?qū)ο蟪绦蚓幊踢@一章還云里霧里的,沒(méi)有領(lǐng)會(huì)多態(tài)的奧妙,學(xué)完 Java 之后回頭再看這一章發(fā)現(xiàn)對(duì)多態(tài)有了更好的理解。好記性不如爛筆頭,本篇博客將會(huì)對(duì) C++ 的多態(tài)機(jī)制做一個(gè)不太詳細(xì)的總結(jié),希望下一次不需要從頭再看一遍《C++ Primer》了 _(:з」∠)_。
多態(tài)
多態(tài)離不開(kāi)繼承,首先來(lái)定義一個(gè)基類?Animal
,里面有一個(gè)虛函數(shù)?speak()
:
class Animal { public: Animal() = default; Animal(string name) : m_name(name) {} virtual ~Animal() = default; virtual void speak() const { cout << "Animal speak" << endl; } string name() const { return m_name; } private: string m_name; };
接著定義子類?Dog
,并重寫虛函數(shù),由于構(gòu)造函數(shù)無(wú)法繼承,所以使用?using
?來(lái) “繼承” 父類的構(gòu)造函數(shù)。和父類相比,Dog
?還多了一個(gè)?bark()
?方法。
class Dog : public Animal { public: using Animal::Animal; // 可加上 override 聲明要重寫虛函數(shù),函數(shù)簽名必須和基類相同(除非返回類自身的指針或引用) void speak() const override { cout << "Dog bark" << endl; } void bark() const { cout << "lololo" << endl; } };
向上轉(zhuǎn)型
我們?cè)诙焉蟿?chuàng)建一個(gè)?Dog
?對(duì)象,并將地址賦給一個(gè)?Animal
?類型的指針。由于指針指向的是個(gè)?Dog
?對(duì)象,調(diào)用?speak()
?方法時(shí),實(shí)際上調(diào)用的是底層狗狗重寫之后的?speak()
?方法,而不是基類?Animal
?的?speak()
。也就是說(shuō)編譯時(shí)不會(huì)直接確定要調(diào)用的是哪個(gè)?speak()
?,要在運(yùn)行時(shí)綁定。
Animal* pa = new Dog("二哈"); pa->speak(); // 調(diào)用的是 Dog::speak pa->Animal::speak(); // 強(qiáng)制調(diào)用基類的 speak
利用運(yùn)行時(shí)綁定這一特點(diǎn),我們將基類的析構(gòu)函數(shù)定義為虛函數(shù),這樣子類對(duì)象在析構(gòu)的時(shí)候就能調(diào)用自己的虛函數(shù)了。
雖然?pa
?指向的是一個(gè)?Dog
?對(duì)象,但是不能使用?bark()
?方法。因?yàn)?pa
?是一個(gè)?Animal
?類型的指針,在編譯時(shí)編譯器會(huì)跳過(guò)?Dog
?而直接在?Animal
?的作用域中尋找?bark
?成員,結(jié)果發(fā)現(xiàn)并不存在此成員而報(bào)錯(cuò)。
要實(shí)現(xiàn)向上轉(zhuǎn)型不止能用指針,引用同樣可以實(shí)現(xiàn)。但是如果寫成以下這種形式,實(shí)質(zhì)上是調(diào)用了拷貝構(gòu)造函數(shù),會(huì)用?Dog
?的基類部分來(lái)初始化?Animal
?對(duì)象,和向上轉(zhuǎn)型沒(méi)有任何關(guān)系,之后調(diào)用的就是底層?Animal
?對(duì)象的?speak()
?方法:
Dog dog("二哈"); Animal animal = dog; animal.speak(); // 調(diào)用的是 Animal::speak
向下轉(zhuǎn)型
要想調(diào)用底層?Dog
?對(duì)象的?bark()
?方法,我們需要將?pa
?強(qiáng)轉(zhuǎn)為?Dog
?類型的指針。一種方法是使用?static_cast
?進(jìn)行靜態(tài)轉(zhuǎn)換,另一種這是使用?dynamic_cast
?進(jìn)行運(yùn)行時(shí)轉(zhuǎn)換。相比于前者,dynamic_cast<type *>
?轉(zhuǎn)換失敗的時(shí)候會(huì)返回空指針,而?dynamic_cast<type &>
?則會(huì)報(bào)?bad_cast
?錯(cuò)誤,因此更加安全。
Dog* pd_ = static_cast<Dog *>(pa); pd_->bark(); if (Dog* pd = dynamic_cast<Dog*>(pa)) { pd->bark(); } else { cout << "轉(zhuǎn)換失敗" << endl; }
作用域
子類的作用域是嵌套在父類里面的,在子類的對(duì)象上查找一個(gè)成員時(shí),會(huì)現(xiàn)在子類中查找,如果沒(méi)找到才回去父類中尋找。由于作用域的嵌套,會(huì)導(dǎo)致子類隱藏掉父類中的同名成員。比如下述代碼:
class Animal { public: virtual void speak() const { cout << "Animal speak" << endl; } }; class Dog : public Animal { public: // void speak() const override { cout << "Dog speak" << endl; } void speak(string word) const { cout << "Dog bark: " + word << endl; } }; int main(int argc, char const* argv[]) { Animal* pa = new Dog(); Dog* pd = new Dog(); // pd->speak(); 報(bào)錯(cuò) pd->speak("666"); // Dog::speak 隱藏了 Animal::speak return 0; }
我們?cè)诟割愔卸x了一個(gè)虛函數(shù)?void speak()
,子類中沒(méi)有重寫它,而是定義了另一個(gè)同名但是參數(shù)不同的函數(shù)?void speak(string word)
。這時(shí)候子類中的同名函數(shù)會(huì)隱藏掉父類的虛函數(shù),如果寫成?pd->speak()
,編譯器會(huì)先在子類作用域中尋找名字為?speak
?的成員,由于存在?speak(string word)
,它就不會(huì)接著去父類中尋找了,接著進(jìn)行類型檢查,發(fā)現(xiàn)參數(shù)列表對(duì)不上,會(huì)直接報(bào)錯(cuò)。如果用了 VSCode 的 C/C++ 插件,可以看到參數(shù)列表確實(shí)只有一個(gè),沒(méi)有提示有重載的同名函數(shù)。
要想通過(guò)調(diào)用基類的?speak()
?方法,有兩種方法:
- 向上轉(zhuǎn)型,使用基類的指針?
pa
?來(lái)調(diào)用?pa->speak()
,由于子類沒(méi)有重寫虛函數(shù),所以在動(dòng)態(tài)綁定時(shí)會(huì)調(diào)用父類的虛函數(shù); - 使用作用域符強(qiáng)制調(diào)用父類的虛函數(shù):
pd->Animal::speak()
《C++ Primer》對(duì)名字查找做了一個(gè)非常好的總結(jié):
理解函數(shù)調(diào)用的解析過(guò)程對(duì)于理解 C++ 的繼承至關(guān)重要,假定我們調(diào)用?p->mem()
?(或者?obj.mem()
),則依次執(zhí)行以下4個(gè)步驟:
1.首先確定?p
?(或?obj
) 的靜態(tài)類型。因?yàn)槲覀冋{(diào)用的是一個(gè)成員,所以該類型必 然是類類型。
2.在?p
?(或?obj
?) 的靜態(tài)類型對(duì)應(yīng)的類中查找?mem
。如果找不到,則依次在直接基類中不斷查找直至到達(dá)繼承鏈的頂端。如果找遍了該類及其基類仍然找不到,則編譯器將報(bào)錯(cuò)。
3.一旦找到了?mem
,就進(jìn)行常規(guī)的類型檢查以確認(rèn)對(duì)于當(dāng)前找到的?mem
,本次調(diào)用是否合法。
4.假設(shè)調(diào)用合法,則編譯器將根據(jù)調(diào)用的是否是虛函數(shù)而產(chǎn)生不同的代碼:
- 如果mem是虛函數(shù)且我們是通過(guò)引用或指針進(jìn)行的調(diào)用,則編譯器產(chǎn)生的代 碼將在運(yùn)行時(shí)確定到底運(yùn)行該虛函數(shù)的哪個(gè)版本,依據(jù)是對(duì)象的動(dòng)態(tài)類型。
- 反之,如果?
mem
?不是虛函數(shù)或者我們是通過(guò)對(duì)象(而非引用或指針)進(jìn)行的調(diào)用,則編譯器將產(chǎn)生一個(gè)常規(guī)函數(shù)調(diào)用。
原文鏈接:https://www.cnblogs.com/zhiyiYo/p/16387116.html
相關(guān)推薦
- 2022-04-02 IDEA集成Docker實(shí)現(xiàn)打包的方法_docker
- 2022-08-29 .NET?Core讀取配置文件_實(shí)用技巧
- 2024-04-06 Linux如何清理Redis中的緩存
- 2022-11-25 使用openssl實(shí)現(xiàn)私有CA的搭建和證書的頒發(fā)_相關(guān)技巧
- 2022-05-25 Python異常處理如何才能寫得優(yōu)雅(retrying模塊)_python
- 2022-05-11 Spring數(shù)據(jù)源及注解開(kāi)發(fā)
- 2023-03-16 淺析Kotlin使用infix函數(shù)構(gòu)建可讀語(yǔ)法流程講解_Android
- 2022-07-16 訓(xùn)練YOLOX時(shí),出現(xiàn)“BrokenPipeError: [Errno 32] Broken pip
- 最近更新
-
- 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)證過(guò)濾器
- 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)程分支