》多態定義的概念是:當不同的對象收到相同的消息時做出了不同工作這種現象就叫做多態。
》那麼在C++中是如何實現多態的呢?
首先多態分爲編譯時多態和運行時多態。在這裏還有一個連編的概念,連編即把函數名和函數體中的代碼連接在一起的過程。靜態連編就是在編譯階段完成的連編,編譯時多態就是通過靜態連編完成的。靜態連編時,系統通過實參與形參進行匹配,對於同名的重載函數就根據參數上的差異進行區分,然後進行連編,從而實現多態性。運行時多態使用動態連編實現的,動態連編是指運行階段完成的連編。即當程序調用某一個函數名時,纔去尋找和連接其相對應的程序代碼。
在C++中,編譯時多態性主要是通過函數重載和運算符重載實現的,運行時多態性主要是通過虛函數來實現的。
虛函數的引入:
class Base { public: Base(int x,int y) :a(x) ,b(y) {} void show() { cout << "Base----" << endl; cout << a << " " << b << endl; } private: int a; int b; }; class Deriver :public Base { public: Deriver(int x, int y, int z) :c(z) ,Base(x,y) {} void show() { cout << "Deriver----" << endl; cout << c << endl; } private: int c; }; void main() { Base mb(60, 60), *pc; Deriver mc(10, 20, 30); pc = &mb; pc->show(); pc = &mc; pc->show(); }
結果如下:
並不是我們所預想的,這說明,不管指針pc當前指向哪個對象(基類的或派生類的),“pc->show()”調用的都是基類定義的show()函數。
》虛函數的作用和定義:
虛函數首先是基類的成員函數,但這個成員函數前面加上關鍵字virtual.並在派生類中被重載,就能實現動態調用的功能。
class Base { public: Base(int x,int y) :a(x) ,b(y) {} virtual void show() { cout << "Base----" << endl; cout << a << " " << b << endl; } private: int a; int b; }; class Deriver :public Base { public: Deriver(int x, int y, int z) :c(z) ,Base(x,y) {} void show() { cout << "Deriver----" << endl; cout << c << endl; } private: int c; }; void main() { Base mb(60, 60), *pc; Deriver mc(10, 20, 30); pc = &mb; pc->show(); pc = &mc; pc->show(); }
結果:
爲什麼把基類中的函數show()定義爲虛函數時,程序的運行結果就正確了呢?這是因爲,關鍵字virtual指示C++編譯器,函數調用“pc->show()”時,要在運行時確定所要調用的函數即要對該調用進行動態連編。我們把使用同一種調用形式“mp->show()”,調用同一類族中不同類的虛函數稱爲動態的多態性,即運行時多態性。
》虛函數的定義
virtual 返回類型 函數名(形參表)
{ 函數體
}
》虛析構函數
class Base { public: ~Base() { cout << "調用基類Base的析構函數" << endl; } }; class Deriver:public Base {public: ~Deriver() { cout << "調用派生類Deriver的析構函數" << endl; } }; int main() { Base* p; p = new Deriver; delete p; return 0; }
結果:
這裏創建了一個派生類的無名對象用基類的指針指向他,在delete指針時只調用了基類的析構函數沒有調用派生類的析構函數原因是當撤銷指針p所指的派生類的無名對象,而調用析構函數時,採用了靜態連編方式,只調用了基類的析構函數。
如果希望程序執行採用動態連編方式,可以將基類的析構函數聲明爲虛析構函數。
virtual ~類名()
{函數體
};
雖然基類和派生類析構函數的名字不同,但是如果將基類析構函數定義爲虛函數,基類派生的所有派生類析構函數也都自動聲明爲虛析構函數。
class Base { public: virtual ~Base() { cout << "調用基類Base的析構函數" << endl; } }; class Deriver:public Base {public: ~Deriver() { cout << "調用派生類Deriver的析構函數" << endl; } }; int main() { Base* p; p = new Deriver; delete p; return 0; }
結果:
當基類析構函數聲明爲虛析構函數時結果就符合我們所期望的,這是由於virtual關鍵字要求delete p時進行動態連編,先調用派生類析構函數再調用基類析構函數實現動態運行時多態。
》多繼承與虛函數
C++語言中,每個有虛函數的類或者虛繼承的子類,編譯器都會爲它生成一個虛擬函數表(簡稱:虛表),表中的每一個元素都指向一個虛函數的地址。(注意:虛表是從屬於類的)
此外,編譯器會爲包含虛函數的類加上一個成員變量,是一個指向該虛函數表的指針(常被稱vptr),每一個由此類別派生出來的類,都有這麼一個vptr。虛表指針是從屬於對象的。也就是說,如果一個類含有虛表,則該類的所有對象都會含有一個虛表指針,並且該虛表指針指向同一個虛表。
虛表的內容是依據類中的虛函數聲明次序--填入函數指針。派生類別會繼承基礎類別的虛表(以及所有其他可以繼承的成員),當我們在派生類中改寫虛函數時,虛表就受了影響;表中的元素所指的函數地址將不再是基類的函數地址,而是派生類的函數地址。
》純虛函數與抽象類
聲明 virtual 函數類型 函數名(參數表)=0;
含有純虛函數的類稱爲抽象類。
那麼純虛函數存在的作用是什麼呢?
純虛函數只是在基類中爲其派生類保留一個函數的名字,以便派生類根據需要對它進行重新定義。純虛函數沒有函數體,它的後邊“=0”並不表示函數的返回值爲0,他只是起形式上的作用,告訴編譯系統“這是純虛函數”,純虛函數不具備函數的功能,不能被調用。