Virtual虛函數
- 在面向對象的C++語言中,虛函數(virtual?function)是一個非常重要的概念。因為它充分體現了面向對象思想中的繼承和多態性這兩大特性,在C++語言里應用極廣。
- 多態性:其含義就是多種形式;將具有繼承關系的多種類型稱之為多態模型,因為使用者可以使用這種類型的多種形式,但是不需要在意他們之間的差異。歸根結底,引用或指針的靜態類型與動態類型的不一致是C++語言支持多態性的根本原因。
- 在派生類中覆蓋了某一個虛函數,可以再一次使用virtual關鍵字指出該函數的性質。如果將一個函數聲明為虛函數,那么這個函數在所有的派生類別中都是虛函數。一個派生類的函數如果覆蓋了某個繼承而來的虛函數,則它的形參類型必須要和被覆蓋的函數完全一致。同樣,派生類中的虛函數返回的類型必須和基類的類型是一致的,但是這個存在一個例外。當類的虛函數返回的類型是類本身的指針或者引用的時候,上述規則無效。
- 什么是虛函數呢?虛函數是指一個類中你希望重載的成員函數,當你用一個基類指針或引用指向一個繼承類對象的時候,你調用一個虛函數,實際調用的是繼承類的版本。?——摘自MSDN
#include <cstring>
#include <string>
#include <iostream>
#include <stdio.h>
#include <conio.h>
using namespace std;class Parent{public:char data[20];void Function1();virtual void Function2(); //這里的Function2是虛擬函數}parent;void Parent::Function1() {printf("This is parent,function1\n");
}void Parent::Function2() {printf("This is parent,function2\n");
}class Child:public Parent{void Function1();void Function2();
}child;void Child::Function1(){printf("This is child,function1\n");
}void Child::Function2() {printf("This is child,function2\n");
}
int main(int argc,char* argv[]){Parent *p;//定義了一個基類指針if(_getch() == 'c'){p = &child; // 如果用戶輸入一個小寫的字母c,指向繼承類對象}else{p = &parent; // 否則指向一個基類對象}p->Function1(); // 這里編譯時會直接給出Parent::Function1()的入口地址p->Function2(); // 注意這里,執行的是哪一個Function2?}
- 為什么會有第一行的結果呢?因為是用一個Parent類的指針調用函數Fuction1(),雖然實際上這個指針指向的是Child類的對象,但編譯器無法知道這一事實(直到運行的時候,程序才可以根據用戶的輸入判斷出指針指向的對象),它只能按照調用Parent類的函數來理解并編譯,所以看到了第一行的結果。
- 那么第二行的結果又是怎么回事呢?注意到,Function2()函數在基類中被virtual關鍵字修飾,也就是說,它是一個虛函數。虛函數最關鍵的特點是“動態聯編”,它可以在運行時判斷指針指向的對象,并自動調用相應的函數。
- 如果在運行上面的程序時任意輸入一個非c的字符,結果如下:
- 請注意看第二行,它的結果出現了變化。程序中僅僅調用了一個Function2()函數,卻可以根據用戶的輸入自動決定到底調用基類中的Function2還是繼承類中的Function2,這就是虛函數的作用。在MFC中,很多類都是需要繼承的,它們的成員函數很多都要重載,比如編寫MFC應用程序最常用的CView::OnDraw(CDC*)函數,就必須重載使用。把它定義為虛函數(實際上,在MFC中OnDraw不僅是虛函數,還是純虛函數),可以保證時刻調用的是用戶自己編寫的OnDraw。虛函數的重要用途在這里可見一斑。
- PS:一定要注意“靜態聯翩 ”和“ 動態聯編 ”的區別,對于我來說,若沒有在VC6.0中親自去測試,憑自己的感覺,當在鍵盤中輸入“c”時,因為雖然實際上這個指針指向的是Child類的對象,但編譯器無法知道這一事實,它只能按照調用Parent類的函數來理解并編譯,所以我們看到了第一行的結果。第二行中調用了子類的function2,完全是因為virtual 的功能,virtual實現了動態聯編,它可以在運行時判斷指針指向的對象,并自動調用相應的函數。當然,如果執行的是p=&parent; 這一句,該指針很明顯的是指向父類,那么肯定調用的是父類的方法?
final和override說明符
- 派生類如果定義了一個函數與基類的虛函數的名字相同但是形參列表不同,這仍然是一個合法的行為,需要使用關鍵字override來說明派生類中的虛函數。
- 如果將某個函數指定為final,則之后任何嘗試覆蓋該函數的操作都會引發錯誤。
虛函數和默認實參
- 虛函數使用默認實參,則基類和派生類中定義的默認實參最好一致。
回避虛函數的機制
- 如果希望對于虛函數的調用不是動態的綁定,而是強迫其執行虛函數的某一個特定的版本。可以使用作用域運算符來實現
- double undiscounted = baseP->Quote::net_price(43);//強行調用基類中定義的函數版本,而不管baseP的動態類型是什么
- 通常情況下,只有成員函數(或友元函數)中的代碼才需要使用作用域運算符來回避虛函數的機制。如果一個派生類虛函數需要調用它的基類版本,但是沒有使用作用域運算符,則在運行的時候該調用會被解析成對于派生類的版本自身的調用,從而導致無限的遞歸。
?
?
?
?
?