北京家裝設計師排名北京網站優(yōu)化推廣方案
💓博主CSDN主頁:杭電碼農-NEO💓
?
?專欄分類:C++從入門到精通?
?
🚚代碼倉庫:NEO的學習日記🚚
?
🌹關注我🫵帶你學習C++
? 🔝🔝
多態(tài)
- 1. 前言
- 2. 多態(tài)的概念以及定義
- 3. 多態(tài)的實例調用情況
- 4. 構成多態(tài)的兩個特例
- 5. 多態(tài)的底層原理分析(一)
- 6. 多態(tài)底層原理分析(二)
- 7. 多態(tài)中的兩個關鍵字
- 8. 抽象類以及虛函數的幾個結論
- 9. 總結以及拓展
1. 前言
繼承和多態(tài)這兩兄弟常常一起出現(xiàn)
繼承是實現(xiàn)多態(tài)的前提!
本章重點:
本篇文章著重講解多態(tài)的概念以及
定義,多態(tài)的底層原理和析構函數重寫
以及函數重寫的兩個例外條件
多繼承中的虛函數表關系.其中,簡單介紹
的部分有抽象類的概念以及定義和
繼承與多態(tài)中的兩個新增關鍵字
注:如果你不知道什么是繼承,或繼承
的知識掌握不牢固,請先閱讀下面文章:
C++繼承深度剖析
2. 多態(tài)的概念以及定義
概念:
通俗來說,多態(tài)就是多種狀態(tài)
父子對象完成相同任務會產生不同的結果
比如:
學生和普通人都去買門票
學生是半價,而普通人是全價
在繼承中構成多態(tài)要有兩個條件:
- 必須通過基類的指針或引用調用虛函數
- 被調用的函數必須是虛函數
并且子類的虛函數要被重寫
現(xiàn)在的你可能有一萬個問號
什么是虛函數?什么是重寫?
沒關系,我們一步一步講!
關鍵字virtual加在成員函數前
這個成員函數就是虛函數!
虛函數的重寫(也叫覆蓋)
:
派生類中有一個跟基類完全相同的虛函數(即派生類虛函數與基類虛函數的,返回值類型、函數名字、參數列表完全相同),稱子類的虛函數重寫了基類的虛函數
class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "買票-半價" << endl; }
};
上面的代碼中,BuyTicket函數就被重寫了!
概念講完,下一步進行實戰(zhàn)!
3. 多態(tài)的實例調用情況
構成多態(tài)的條件就兩個,一定要熟記!
一定要熟記!一定要熟記!重要的事情說三遍
下面是多態(tài)的實例:
class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "買票-半價" << endl; }
};int main()
{Person* p1 = new Person;Person* p2 = new Student;p1->BuyTicket();p2->BuyTicket();return 0;
}
我們知道一個事實:
基類的指針或引用可以指向/引用
子類的對象,我們稱為切片
p1和p2是基類指針,它們調用的
函數恰好還被重寫了,所以這里符合
多態(tài),p1指針指向的內容是Person
所以它調用Person中的函數,然而p2
指針指向的內容是Student,所以它
調用的是Student中的函數!
依次打印:"買票-全家","買票-半價"
4. 構成多態(tài)的兩個特例
特例一:
子類的虛函數不寫virtual
依舊構成多態(tài)
class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:void BuyTicket() { cout << "買票-半價" << endl; }
};
Person* p1 = new Person;
Person* p2 = new Student;
p1->BuyTicket();
p2->BuyTicket();
這樣寫也是構成多態(tài)的!
特例二:
基類與派生類虛函數返回值類型不同
也可以構成多態(tài)(返回值必須滿足某種條件)
class A{};
class B : public A {};class Person {
public:virtual A* f() {return new A;}
};
class Student : public Person {
public:virtual B* f() {return new B;}
};
父類的返回值要返回父類
子類的返回值要返回子類
注意事項1:
父類不寫virtual,而子類的同名
函數寫了virtual,這是不構成多態(tài)的!
class Person {
public:void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:virtual void BuyTicket() { cout << "買票-半價" << endl; }
};
不構成多態(tài)!
注意事項2:
在繼承體系中,父子類的同名
函數不構成重寫就構成隱藏,不可能構成重載!
5. 多態(tài)的底層原理分析(一)
如果你單純的認為Base類只有一個
整型變量占用空間的話,那你就上當啦!
事實上在32位機器下,這里的結果是8
在64位機器下,這里的結果是16!
這是因為它除了有一個變量外,還有
一個指針,此指針指向一個虛函數表
我們通過以下的代碼來觀察內存:
class A
{
public:virtual void func1(){cout << "父類func1";}
private:int _a;
};
class B : public A
{
public:virtual void func1(){cout << "子類func1";}
private:int _b;
};int main()
{A a;B b;return 0;
}
此指針叫虛表指針:vfptr,也就是
virtual function ptr
這個指針并不是直接指向虛函數的地址
而是指向一個虛函數表,可以理解位一個
數組,此數組中存放著此對象中所有的虛
函數的地址,它們的關系可以用下圖表示:
注:不管有沒有繼承體系或多態(tài)
只要有虛函數就有虛表!
6. 多態(tài)底層原理分析(二)
現(xiàn)在得出一個結論:有虛函數的
類對象中還存放了一個虛表指針!
那么父類和子類的虛表指針和指向
的內容有什么不同或相同處嗎?
形成多態(tài)現(xiàn)象的原理又是什么?
我來一一解答這些問題:
通過下面的代碼來觀察內存情況
得出父子類虛表的關聯(lián):
class A
{
public:virtual void func1()cout << "父類func1";virtual void func2()cout << "父類func2";
private:int _a;
};
class B : public A
{
public:virtual void func1()cout << "子類func1";
private:int _b;
};
int main()
{A a;B b;return 0;
}
請看下圖觀察情況:
結論:
父類和子類的虛表指針是不同的
證明父子類各有一張?zhí)摵瘮当?
函數func1在子類中被重寫了,所以
父子類虛表中的func1函數地址是不同的
函數func2沒有被子類重寫,所以
父子類虛表中的func2函數地址是相同的
拓展結論:同一個類的不同對象共用一個虛表
多態(tài)的原理深度剖析:
當一個函數A被重寫時,它的父類虛表存放
父類函數A的地址,子類虛表存放的是子類
函數A的地址!
當父類的指針或引用指向子類空間時
調用虛函數時,會到指向對象的虛表中
中找到對應的虛函數地址,進行調用!
拓展結論:
父子類都只有A函數或無函數時
-
若父類寫了虛函數A,而子類
甚至沒有寫函數A,此時子類對象中
存儲的虛函數地址與父類相同 -
若父類甚至沒有寫函數A,而子類
直接寫了虛函數A,則父類對象中沒有
虛表,而子類對象中有虛表(存放A)
7. 多態(tài)中的兩個關鍵字
final:
修飾虛函數,表示該虛函數不能被重寫
override:
檢查子類類虛函數是否重寫了
基類虛函數如果沒有重寫編譯報錯
8. 抽象類以及虛函數的幾個結論
抽象類概念:
在虛函數的后面寫上 =0 ,則這個函數為純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。派生類繼承后也不能實例化出對象,只有重寫純虛函數,派生類才能實例化出對象。純虛函數規(guī)范了派生類必須重寫
抽象類的只需了解概念,實際中
使用到的場景很少
關于虛函數的幾個小結論:
- 析構函數最好定義為虛函數
- 構造函數不能定義為虛函數
- 靜態(tài)成員函數不能是虛函數
- 內聯(lián)函數(inline)不能是虛函數
為什么說析構函數最好定義為虛函數?
請看下面的例子:
class Person {
public:virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生類Student的析構函數重寫了Person的析構函數
//下面的delete對象調用析構函數,才能構成多態(tài)
//才能保證p1和p2指向的對象正確的調用析構函數。
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}
若析構函數不是虛函數,delete ptr2時
不符合多態(tài),ptr2是Person類型指針
就只會調用Person類的析構,會有問題
若析構函數是虛函數,delete ptr2時
構成多態(tài)的條件,指針指向父類的對象
就調用父類的析構,指向子類的對象
就調用子類的析構,這樣才是正確的!
9. 總結以及拓展
多態(tài)在校招的筆試面試中考察的
非常之多,很多面試官都喜歡在這
上面考察學生的掌握C++語法的程度
所以同學們請耐心學習!
拓展閱讀:
多繼承場景下的多態(tài)