一.定義
虛函數: 在某基類中聲明為 virtual 並在一個或多個派生類中被重新定義的成員函數,可實現函數成員的動態重載。
純虛函數: 純虛函數是一種特殊的虛函數,在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。含有純虛函數的類被稱為抽象類(abstract class)
二.格式
虛函數:virtual <函數返回類型>< 函數名>(<參數表>) {函數體};
純虛函數:virtual <函數返回類型><函數名>(<參數表>)=0;
三.不同點
1.虛函數可以直接使用,也可以在子類中重載以多態的形式調用,但純虛函數在基類中只有聲明沒有定義,所以只能在子類中實現了該函數才可以以多態的形式調用。
2.虛函數在子類中可以不被重載,但是純虛函數必須在子類中實現。
3.包含純虛函數的類成為抽象類,這種類不能聲明對象,只是作為基類為派生類服務。除非在派生類中完全實現基類中所有的的純虛函數,否則,派生類也變成了抽象類,不能實例化對象。
虛函數以及構造函數執行順序的一些特性(例題)
構造函數:先構造基類,再構造派生類;
析構函數:先析構派生類,再析構基類。
例題一(函數執行順序)
下面這段代碼會打印出什么?
class A { public: A() { printf("A "); } ~A() { printf("deA "); } }; class B { public: B() { printf("B "); } ~B() { printf("deB "); } }; class C: public A, public B//按照順序執行構造函數,先執行A,再執行B { public: C() { printf("C "); } ~C() { printf("deC "); } }; int main() { A *a = new C();//指針不是對象,只有A a這樣才是創建對象 delete a; return 0; }
正確答案: A 你的答案: B (錯誤)
(A) A B C deA
(B) C A B deA
(C) A B C deC
(D) C A B deC
解析:構造函數的執行先執行父類,再執行子類。析構函數的執行順序相反,A B的析構函數不是虛函數,所以不會執行子類的虛函數。
例題二(函數執行順序)
解析:
1.當派生類中不含對象成員時
在創建派生類對象時,構造函數的執行順序是:基類的構造函數→派生類的構造函數;
析構函數相反。
2.當派生類中含有對象成員時
在定義派生類對象時,構造函數的執行順序:基類的構造函數→對象成員的構造函數→派生類的構造函數;
析構函數相反。
例題三(虛函數調用順序)
dynamic_cast里面必須含有虛函數才行,有虛函數的析構函數,才會先析構子類,再析構基類,不然只會析構自己所在的類。
解析:創建一個類對象c,然后動態類型轉換,讓一個B *b1指針指向c,再一次動態類型轉換,讓一個基類A *a2指針指向b1,當delete a2時,調用析構函數,但是基類A的析構函數不是虛函數,所以只調用A的析構函數,結果應該是:~A()
動態的多態通過虛函數實現,基類指針指向派生類的對象,若指針調用的函數派生類中存在,且在基類中聲明為虛函數,則調用的函數是派生類中的函數。 析構函數總是要聲明為虛函數,這樣析構時,先調用派生類的析構函數,再調用基類的析構函數,防止內存造成泄露 。A類的析構函數未聲明為虛函數,所以A類的指針,只可以調用A類的析構函數
例題四(純虛函數)
解析:純虛函數格式:virtual <類型> <函數名> (<參數表>) = 0;