網站首頁 編程語言 正文
多態性
多態性是面向對象程序設計的關鍵技術之一,若程序設計語言不支持多態性,不能稱為面向對象的語言,利用多態性技術,可以調用同一個函數名的函數,實現完全不同的功能
在C++中有兩種多態性:
- 編譯時的多態
通過函數的重載和運算符的重載來實現的
- 運行時的多態性
運行時的多態性是指在程序執行前,無法根據函數名和參數來確定該調用哪一個函數,必須在程序執行過程中,根據執行的具體情況來動態地確定;它是通過類繼承關系public和虛函數來實現的,目的也是建立一種通用的程序;通用性是程序追求的主要目標之一
通過引用或指針調用時,才可以達到運行時的多態
虛函數
虛函數是一個類的成員函數,定義格式如下:
virtual 返回類型 函數名(參數表);
關鍵字virtual
指明該成員函數為虛函數,virtual僅用于類定義中,如虛函數在類外定義,不可加virtual
我們來看下面代碼
class Animal { private: string name; public: Animal(const string& na):name(na) {} public: virtual void eat(){} virtual void walk(){} virtual void tail(){} virtual void PrintInfo(){} string& get_name() { return name; } const string& get_name()const { return name; } }; class Dog :public Animal { private: string owner; public: Dog(const string& ow, const string na) :Animal(na), owner(ow) {} virtual void eat() { cout << "Dog Eat: bone" << endl; } virtual void walk() { cout << "Dog Walk: run" << endl; } virtual void tail() { cout << "Dog Tail: wangwang" << endl; } virtual void PrintInfo() { cout << "Dog owner" << owner << endl; cout << "Dog name:" << get_name() << endl; } }; class Cat :public Animal { private: string owner; public: Cat(const string& ow, const string na) :Animal(na), owner(ow) {} virtual void eat() { cout << "Cat Eat: fish" << endl; } virtual void walk() { cout << "Cat Walk: silent" << endl; } virtual void tail() { cout << "Cat Tail: miaomiao" << endl; } virtual void PrintInfo() { cout << "Cat owner: " << owner << endl; cout << "Cat name: " << get_name() << endl; } }; // 需要公有繼承 公有繼承代表是一個的意思 // 需要引用或指針調用 void fun(Animal& animal) { animal.eat(); //對象名稱.虛方法() animal.walk(); animal.tail(); animal.PrintInfo(); } int main() { Dog dog("zyq", "hashiqi"); //const string& ow = "zyq" Cat cat("zyq", "bosimao"); fun(dog); fun(cat); return 0; }
在這里我們可以看到,當我們調用fun()函數時,傳入dog對象則調用Dog的方法,傳入cat調用Cat方法;這就是所謂的運行時的多態
要想達到運行時的多態(晚綁定)需要滿足:
- 公有繼承
- 有虛函數
- 必須以指針或引用方式調用虛函數
若發生早綁定,則會調用Animal類型的方法
成員函數應盡可能的設置為虛函數,但必須注意一下幾條:
1.派生類中定義虛函數必須與基類中的虛函數同名外,還必須同參數表,同返回類型;否則被認為是重載,而不是虛函數。如基類中返回基類指針,派生類中返回派生類指針是允許的,這是一個例外
2.只有類的成員函數才能說明為虛函數,這是因為虛函數僅適用于有繼承關系的類對象
3.靜態成員函數,是所有同一類對象公有,不受限于某個對象,不能作為虛函數(友元函數也不可以)
4.實現動態多態性時,必須使用基類類型的指針變量或引用,使該指針指向該基類的不同派生類的對象,并通過該指針指向虛函數,才能實現動態的多態性
5.內聯函數每個對象一個拷貝,無映射關系,不能作為虛函數
6.析構函數可定義為虛函數,構造函數不可以定義為虛函數,因為在調用構造函數時對象還沒有完成實例化;在基類中及其派生類中都動態分配的內存空間時,必須把析構函數定義為虛函數,實現撤銷對象時的多態性
7.函數執行速度要稍慢一些,為了實現多態性,每一個派生類中均要保存相應虛函數的入口地址表,函數的調用機制也是間接實現;所以多態性總要付出一定代價,但通用性是一個更高的目標
8.如果定義放在類外,virtual只能加在函數聲明前面,不能加載函數定義前面;正確的定義必須不包括virtual
虛函數是覆蓋,同名函數是隱藏
虛函數編譯過程
class Object { private: int value; public: Object(int x = 0) :value(x) {} virtual void add() { cout << "Object::add" << endl; } virtual void fun() { cout << "Object::fun" << endl; } virtual void print()const { cout << "Object::print" << endl; } }; class Base:public Object { private: int sum; public: Base(int x = 0) :Object(x+10),sum(x) {} virtual void add() { cout << "Base::add" << endl; } virtual void fun() { cout << "Base::fun" << endl; } virtual void print()const { cout << "Base::print" << endl; } }; int main() { }
此處虛函數表中進行的是同名覆蓋,而不像繼承關系中,同名成員進行隱藏,就近處理;虛函表僅有一份,存在數據區
在主函數創建對象
int main() { Base base(10); Object* op = &base; }
可以看到base的大小為12字節,因為其中基類對象Object,添加了虛表變為了8字節,且在構建過程,首先構建Object基類,此時虛表指針指向Object的虛表,而接著構建Base類的時候,會將虛表指針修改為指向Base的虛表
也就是,當有虛函數時,構造函數除了構建對象初始化對象的數據成員外,還會將虛表的地址給到虛表指針;同時這也是構造函數不可以作為虛函數的原因
int main() { Base base(10); Object* op = NULL; Object obj(0); op = &base; op->add(); //指針或引用調動,則采用運行時多態 op->fun(); op->print(); obj = base; obj.add(); //對象直接調動,則采用編譯時多態 obj.fun(); obj.print(); }
也就是我們通過,對象名.方法
?的方式調用虛函數,則通過編譯時多態的方式
運行時的多態,是通過查詢虛表進行調用;下面通過匯編進一步查看
只有進行以指針調用或引用調用的時候才會對虛表進行查詢
三層繼承
class Object { private: int value; public: Object(int x = 0) :value(x) {} virtual void add() { cout << "Object::add" << endl; } virtual void fun() { cout << "Object::fun" << endl; } virtual void print()const { cout << "Object::print" << endl; } void fn_a() { fun(); } }; class Base:public Object { private: int sum; public: Base(int x = 0) :Object(x+10),sum(x) {} virtual void add() { cout << "Base::add" << endl; } virtual void fun() { cout << "Base::fun" << endl; } virtual void show() { cout << "Base::show" << endl; } }; class Test :public Base { private: int num; public: Test(int x = 0) :Base(x + 10) {} virtual void add() { cout << "Test::add" << endl; } virtual void print() const { cout << "Test::print" << endl; } virtual void show() { cout << "Test::show" << endl; } };
我們可以看到虛函數表,當我們構建派生類,會復制基類的虛函數表,將虛表指針指向新的虛函數表,并且將同名的虛函數進行覆蓋
依舊使用上面代碼
/* void fn_a() { fun(); //this->fun(); 屬于動態綁定! } */ int main() { Test t1; Base base; Object obj; t1.fn_a(); //fn_a(&t1); base.fun_a(); obj.fn_a(); return 0; }
這里依然屬于動態綁定,所以調用虛表指針指向的相對應類的虛表
總結
原文鏈接:https://blog.csdn.net/XXXTENTAC1ON/article/details/123716414
相關推薦
- 2022-07-24 elment-ui的上傳組件圖片不回顯
- 2022-10-11 C++函數模板與類模板相同與不同介紹_C 語言
- 2022-04-01 C#中逆變的實際應用場景詳解_C#教程
- 2022-06-27 詳解Python中while無限迭代循環方法_python
- 2022-06-20 C語言三種方法解決輪轉數組問題_C 語言
- 2022-08-31 MongoDB中實現多表聯查的實例教程_MongoDB
- 2022-07-18 C語言枚舉類型
- 2023-12-11 注解開發Mybatis
- 最近更新
-
- 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同步修改后的遠程分支