在上篇博客中,簡單提了一下C++的第一個特點繼承,今天就來聊聊第二個特點:多態
一、多態的概念
1、什麼是多態呢?簡單來說,多態就是面向對象的重要特性,也可以說是同一種事物的不同體現,或者是同一事物表現出得多種形態。
2、多態的分類
靜態多態:編譯器在編譯期間完成,編譯器根據函數實參的類型(可能會進行隱式轉換)。
動態多態:在程序運行期間(非編譯期間)判斷所引用對象的實際類型,根據其實際類型調用相應的方法。
3、動態多態實現的兩個條件:
(1)調用虛函數--->一定在派生類中對基類的虛函數進行重寫
重寫(覆蓋):在繼承體系中,如果基類中有虛函數,在派生類中有和基類虛函數原型相同的虛函數。
重寫的特例:1>協變:基類返回基類指針,派生類返回派生類指針
2>析構函數:基類調用基類析構函數 ~Base() 派生類調用派生類構造函數 ~Derived()
(2)通過基類的指針或引用來調用虛函數
二、動態多態的實現及其原理
多態通過虛函數結合動態綁定來實現。
虛函數:通過關鍵字virtual所修飾的成員函數。
在講動態多態的實現之前,必須先來講一下虛表以及上篇提過的對象模型
先看一下下面的代碼
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
{
public:
void Test1()
{
cout << "A::Test1()" << endl;
}
};
class B
{
public:
virtual void Test1()
{
cout << "B::Test1()" << endl;
}
};
int main()
{
cout <<"A的大小" << sizeof(A) << endl;
cout <<"B的大小" << sizeof(B) << endl;
system("pause");
return 0;
}
輸出結果及分析1、普通函數的調用:call函數地址
虛函數的調用: 通過查詢虛表
2、先來提兩個問題
1>同一個類的對象是否公用一張虛表? 答案:是,公用同一張虛表
2>派生類是否和基類公用一張虛表? 答案:否,不共用
下面就重點講一下虛表的構造
基類中的虛表:虛函數在類中的申明次序
派生類中的虛表:
1、單繼承
1>先拷貝基類中的虛表
2>檢測派生類中是否對基類中的虛函數進行重寫----是->用派生類中重寫的虛函數來替代相同偏移量位置的基類虛函數
3>在虛表之後添加派生類自己的虛函數
在此處說一下虛函數的調用:1>取對象地址2>取對象前四個字節中的內容—>虛表指針3>傳this指針4>調對應指針
下面就用幾行代碼來通俗的演示及解釋一番
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } private: int _a; }; class B:public A { public: //重寫基類虛函數 virtual void Test1() { cout << "B::Test1()" << endl; } //派生類新定義的虛函數 virtual void Test2() { cout << "B::Test2()" << endl; } private: int _b; }; typedef void(*Pfun)(); //遍歷虛表內容 void Display(const A&a) { Pfun*pfun = (Pfun*)*(int*)&a; while (*pfun) { (*pfun)(); ++pfun; } } void test() { A a; B b; Display(a); Display(b); } int main() { test(); system("pause"); return 0; }
通過對結果的分析可以看出在派生類的對象模型中,不僅繼承了基類中的,而且也對在派生類中重寫的虛函數做了修改,然後再下邊補上了派生類自己的虛函數。
2、多繼承
在第一張虛表之後加上派生類自己的虛函數,其他與單繼承類似。
將派生類的虛函數加在第一張虛表之後的好處:效率快,僅訪問前四個字節就能訪問到派生類虛函數。
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } virtual void Test2() { cout << "A::Test2()" << endl; } private: int _a; }; class B { public: virtual void Test3() { cout << "B::Test3()" << endl; } private: int _b; }; class C:public A,public B { public: //重寫基類虛函數 virtual void Test1() { cout << "C::Test1()" << endl; } virtual void Test3() { cout << "C::Test3()" << endl; } //派生類新定義的虛函數 virtual void Test4() { cout << "C::Test4()" << endl; } private: int _c; }; typedef void(*Pfun)(); //遍歷虛表內容 void Display(const A&a) { //訪問A的虛表 Pfun*pfun = (Pfun*)*(int*)&a; while (*pfun) { (*pfun)(); ++pfun; } //跳過A的內容訪問B的虛表 pfun = (Pfun*)*(int*)((char*)&a + sizeof(A)); while (*pfun) { (*pfun)(); ++pfun; } } void test() { C c; Display(c); } int main() { test(); system("pause"); return 0; }
對代碼的解讀如下:
多繼承:先繼承的基類的虛表在前,再把派生類自己的虛函數加在第一張虛表之後,再把第二個繼承的基類的虛表接在後面。
3、菱形繼承
1)帶虛函數的虛擬繼承
在此繼承的體系下,則對象的對象模型需要再多四個字節,用來存放偏移量表格。
<1>先看派生類僅重寫了基類的虛函數,而無自己的虛函數的情況
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } int _a; }; class B:virtual public A { public: virtual void Test1() { cout << "B::Test1()" << endl; } int _b; }; int main() { cout << sizeof(B) << endl; A a; B b; b._a = 0; b._b = 1; system("pause"); return 0; }
<2>先看派生類不僅重寫了基類的虛函數,而且擁有自己的虛函數的情況
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } int _a; }; class B:virtual public A { public: virtual void Test1() { cout << "B::Test1()" << endl; } virtual void Test2() { cout << "B::Test2()" << endl; } int _b; }; int main() { cout << sizeof(B) << endl; A a; B b; b._a = 0; b._b = 1; system("pause"); return 0; }
2)菱形繼承
class A { public: virtual void Test1() { cout << "A::Test1()" << endl; } virtual void Test2() { cout << "A::Test2()" << endl; } int _a; }; class B1:virtual public A { public: virtual void Test1() { cout << "B1::Test1()" << endl; } virtual void Test3() { cout << "B1::Test3()" << endl; } int _b1; }; class B2 :virtual public A { public: virtual void Test1() { cout << "B2::Test1()" << endl; } virtual void Test4() { cout << "B2::Test4()" << endl; } int _b2; }; class D :public B1,public B2 { public: virtual void Test1() { cout << "D::Test1()" << endl; } virtual void Test3() { cout << "D::Test3()" << endl; } virtual void Test4() { cout << "D::Test4()" << endl; } virtual void Test5() { cout << "D::Test5()" << endl; } int _d; }; typedef void(*FunPtr)(); void Print(A& a) { FunPtr* pfun = (FunPtr*)(*(int*)&a); while (*pfun) { (*pfun)(); pfun++; } cout << endl; } void Print(B1& b1) { FunPtr* pfun = (FunPtr*)(*(int*)&b1); while (*pfun) { (*pfun)(); pfun++; } cout << endl; } void Print(B2& b2) { FunPtr* pfun = (FunPtr*)(*(int*)&b2); while (*pfun) { (*pfun)(); pfun++; } cout << endl; } int main() { cout << sizeof(D) << endl; D d; B1& b1 = d; Print(b1); B2& b2 = d; Print(b2); A& a = d; Print(a); return 0; }
對於上述帶虛函數的菱形繼承的代碼根據內存塊的分析
以上就是我對多態的一點小小總結,希望各位大佬們多指點。