網站首頁 編程語言 正文
前言
最近為了完成數據庫系統的實驗,又復習起了《C++ Primer》,上一次看這本巨著也是大二下的六月份,那時看面向對象程序編程這一章還云里霧里的,沒有領會多態的奧妙,學完 Java 之后回頭再看這一章發現對多態有了更好的理解。好記性不如爛筆頭,本篇博客將會對 C++ 的多態機制做一個不太詳細的總結,希望下一次不需要從頭再看一遍《C++ Primer》了 _(:з」∠)_。
多態
多態離不開繼承,首先來定義一個基類?Animal
,里面有一個虛函數?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
,并重寫虛函數,由于構造函數無法繼承,所以使用?using
?來 “繼承” 父類的構造函數。和父類相比,Dog
?還多了一個?bark()
?方法。
class Dog : public Animal { public: using Animal::Animal; // 可加上 override 聲明要重寫虛函數,函數簽名必須和基類相同(除非返回類自身的指針或引用) void speak() const override { cout << "Dog bark" << endl; } void bark() const { cout << "lololo" << endl; } };
向上轉型
我們在堆上創建一個?Dog
?對象,并將地址賦給一個?Animal
?類型的指針。由于指針指向的是個?Dog
?對象,調用?speak()
?方法時,實際上調用的是底層狗狗重寫之后的?speak()
?方法,而不是基類?Animal
?的?speak()
。也就是說編譯時不會直接確定要調用的是哪個?speak()
?,要在運行時綁定。
Animal* pa = new Dog("二哈"); pa->speak(); // 調用的是 Dog::speak pa->Animal::speak(); // 強制調用基類的 speak
利用運行時綁定這一特點,我們將基類的析構函數定義為虛函數,這樣子類對象在析構的時候就能調用自己的虛函數了。
雖然?pa
?指向的是一個?Dog
?對象,但是不能使用?bark()
?方法。因為?pa
?是一個?Animal
?類型的指針,在編譯時編譯器會跳過?Dog
?而直接在?Animal
?的作用域中尋找?bark
?成員,結果發現并不存在此成員而報錯。
要實現向上轉型不止能用指針,引用同樣可以實現。但是如果寫成以下這種形式,實質上是調用了拷貝構造函數,會用?Dog
?的基類部分來初始化?Animal
?對象,和向上轉型沒有任何關系,之后調用的就是底層?Animal
?對象的?speak()
?方法:
Dog dog("二哈"); Animal animal = dog; animal.speak(); // 調用的是 Animal::speak
向下轉型
要想調用底層?Dog
?對象的?bark()
?方法,我們需要將?pa
?強轉為?Dog
?類型的指針。一種方法是使用?static_cast
?進行靜態轉換,另一種這是使用?dynamic_cast
?進行運行時轉換。相比于前者,dynamic_cast<type *>
?轉換失敗的時候會返回空指針,而?dynamic_cast<type &>
?則會報?bad_cast
?錯誤,因此更加安全。
Dog* pd_ = static_cast<Dog *>(pa); pd_->bark(); if (Dog* pd = dynamic_cast<Dog*>(pa)) { pd->bark(); } else { cout << "轉換失敗" << endl; }
作用域
子類的作用域是嵌套在父類里面的,在子類的對象上查找一個成員時,會現在子類中查找,如果沒找到才回去父類中尋找。由于作用域的嵌套,會導致子類隱藏掉父類中的同名成員。比如下述代碼:
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(); 報錯 pd->speak("666"); // Dog::speak 隱藏了 Animal::speak return 0; }
我們在父類中定義了一個虛函數?void speak()
,子類中沒有重寫它,而是定義了另一個同名但是參數不同的函數?void speak(string word)
。這時候子類中的同名函數會隱藏掉父類的虛函數,如果寫成?pd->speak()
,編譯器會先在子類作用域中尋找名字為?speak
?的成員,由于存在?speak(string word)
,它就不會接著去父類中尋找了,接著進行類型檢查,發現參數列表對不上,會直接報錯。如果用了 VSCode 的 C/C++ 插件,可以看到參數列表確實只有一個,沒有提示有重載的同名函數。
要想通過調用基類的?speak()
?方法,有兩種方法:
- 向上轉型,使用基類的指針?
pa
?來調用?pa->speak()
,由于子類沒有重寫虛函數,所以在動態綁定時會調用父類的虛函數; - 使用作用域符強制調用父類的虛函數:
pd->Animal::speak()
《C++ Primer》對名字查找做了一個非常好的總結:
理解函數調用的解析過程對于理解 C++ 的繼承至關重要,假定我們調用?p->mem()
?(或者?obj.mem()
),則依次執行以下4個步驟:
1.首先確定?p
?(或?obj
) 的靜態類型。因為我們調用的是一個成員,所以該類型必 然是類類型。
2.在?p
?(或?obj
?) 的靜態類型對應的類中查找?mem
。如果找不到,則依次在直接基類中不斷查找直至到達繼承鏈的頂端。如果找遍了該類及其基類仍然找不到,則編譯器將報錯。
3.一旦找到了?mem
,就進行常規的類型檢查以確認對于當前找到的?mem
,本次調用是否合法。
4.假設調用合法,則編譯器將根據調用的是否是虛函數而產生不同的代碼:
- 如果mem是虛函數且我們是通過引用或指針進行的調用,則編譯器產生的代 碼將在運行時確定到底運行該虛函數的哪個版本,依據是對象的動態類型。
- 反之,如果?
mem
?不是虛函數或者我們是通過對象(而非引用或指針)進行的調用,則編譯器將產生一個常規函數調用。
原文鏈接:https://www.cnblogs.com/zhiyiYo/p/16387116.html
相關推薦
- 2022-04-24 基于Python制作一個文件去重小工具_python
- 2022-11-24 Swift?并發修改Sendable?閉包實例詳解_Swift
- 2022-08-14 Redis+Caffeine兩級緩存的實現_Redis
- 2022-11-05 Nginx監控模塊(vts模塊)詳解_nginx
- 2022-10-06 C#中?MessageBox的使用技巧_C 語言
- 2022-07-15 Android?Flutter繪制扇形圖詳解_Android
- 2022-11-03 ios利用RunLoop原理實現去監控卡頓實例詳解_IOS
- 2022-08-10 C++學習之多態的使用詳解_C 語言
- 最近更新
-
- 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同步修改后的遠程分支