在實際開發工作中,為提高**的重用性,編寫通用的功能模組,往往需要設計處理幾種不同物件的通用程式,如示例2.1所示。
示例清單2.1
#include "stdio.h"
#include "stdlib.h"
//定義函式指標型別displayinteger,指向返回值為void,引數列表為(const int)的函式
typedef void( *displayinteger)(const int);
//定義函式,將數字以十進位制形式輸出,該函式型別與displayinteger匹配
void displaydecimal(const int number)
printf("the decimal value is %d\n",number);
//定義函式,將數字以八進位制形式輸出,該函式型別與displayinteger匹配
void displayoctal(const int number)
printf("the octal value is %o\n",number);
//定義函式,將數字以十六進製制形式輸出,該函式型別與displayinteger匹配
void displayhexadecimal (const int number)
printf("the hexadecimal value is %x\n",number);
定義通用的顯示數字函式
displayformat displayinteger函式指標型別,實參可以是以上定義的
3個函式之一。通過傳遞不同的實參,將數字以各種格式輸出
number 準備輸出的數字
void displaynumber(displayinteger displayformat,const int number)
//呼叫以實參傳入的函式,以某種格式輸出整型數字
displayformat(number);
int main(int argc, char* argv)
int number=0;
//如果有數字形式的命令列引數,將其輸出,否則輸出0
if(argc>1)
number=atoi(argv[1]);
//分別以3種格式將數字輸出
displaynumber(displaydecimal,number);
displaynumber(displayoctal,number);
displaynumber(displayhexadecimal,number);
return 0;
命令列c121.exe 50
的輸出結果:
the decimal value is 50
the octal value is 62
the hexadecimal value is 32
示例2.1中定義了乙個通用函式:void displaynumber(displayinteger displayformat, const int number)。
其功能是以各種格式顯示整型數字。只要傳遞適當的實參(函式位址),該函式就能很好地工作。如果客戶需求發生變化,例如增加二進位制格式的輸出,只要增加相應的功能函式,例如,void displaybinary (const int number)即可。而通用函式displaynumber()不必改動。顯然,函式指標displayinteger給該函式增添了靈性,使其得以通用。
其實,以上函式的通用性得益於c++的動態聯編功能,而函式指標不過是該功能的一種應用形式。
在c++編譯時,對於常規的函式呼叫,編譯器在函式的呼叫處插入函式的相對位址,程式執行時可以由函式的相對位址計算出函式的絕對位址,這樣函式可以被正確呼叫。這種在編譯時就確定函式位址的聯編過程叫做靜態聯編。動態聯編是指在程式編譯時,編譯器並不知道函式的相對位址,呼叫函式的相對位址只有在程式執行時才能確定。例如在示例2.1中的displaynumber()函式體內,編譯器並不知道displayformat(number)呼叫的函式位址,真正的位址是在執行時通過實參傳入的。
2.2 引入虛函式
看來,基於動態聯編的機制,使用函式指標就可以編寫出相對通用的程式模組。然而,我們早已開始了物件導向的程式設計,類成為封裝功能模組的基本單位。所以不僅需要對函式指標進行動態聯編,更需要對類指標進行動態聯編。幸運的是,c++的確為開發者提供了這一支援,它就是虛函式。
只要在類的非靜態成員函式前加關鍵字virtual,這一函式就是虛函式。編譯器對於虛函式採用動態聯編方式。
2.2.1 例項:定義虛函式
那麼,如何利用虛函式編寫處理多種物件的通用程式呢?為通俗地闡述這一問題,下面討論如何以虛函式的方式改寫示例2.1。
(1)定義乙個基類,名為cdispdecimal。該類封裝乙個整型資料成員number、乙個虛擬成員函式virtual displayformat()。該虛函式將成員number以十進位制格式輸出。
class cdispdecimal
public:
cdispdecimal(int i)
cdispdecimal()
virtual displayformat()
printf("the decimal value is %d\n",number);
protected:
int number;
(2)從基類cdispdecimal派生出兩個子類,名為cdispoctal、cdisphexadecimal。這兩個類都過載基類的虛函式displayformat(),分別將number以八進位制、十六進製制格式輸出。
class cdispoctal :public cdispdecimal
public:
cdispoctal(int i)
cdispoctal()
virtual displayformat()
printf("the octal value is %o\n",number);
class cdisphexadecimal: public cdispdecimal
public :
cdisphexadecimal(int i)
cdisphexadecimal()
virtual displayformat()
printf("the hexadecimal value is %x\n",number);
因為編譯器對虛函式動態聯編,所以每個類的虛函式要能完成該類特有的功能,上面定義的3個類就是如此。注意,過載虛函式要求所有函式宣告完全一致,即函式名稱和形參列表都一致。否則等同於普通函式的過載,不能實現動態聯編。
2.2.2 例項:編寫通用函式
下面編寫通用函式,它的乙個形參是基類的指標,即cdispdecimal*。函式體內呼叫該基類的虛函式。
void displaynumber( cdispdecimal* displayformat)
//呼叫以實參傳入物件指標的虛函式,以某種格式輸出整型數字
displayformat->displayformat();
從**上也許看不出這個函式有什麼通用的味道,但讀者不要忽略這樣乙個事實:c++編譯器可以直接將派生類的指標轉換為基類指標。這樣,呼叫該函式時,實參不僅可以是cdispdecimal物件的位址,也可以是cdisphexadecimal或dispoctal物件的位址。同時,虛函式採用動態聯編,函式體內呼叫的虛函式displayformat()並不一定是基類定義的,它將由實參決定,也可能是由cdisphexadecimal或dispoctal定義的。
2.2.3 例項:定義主函式
分別以cdispdecimal、cdispoctal、cdisphexadecimal物件的位址為實參呼叫通用函式displaynumber(),以3種格式輸出整數。
int main(int argc, char* argv)
cdispdecimal deci;
cdispoctal octa;
cdisphexadecimal hexa;
//如果有數字形式的命令列引數,將其輸出,否則輸出0
if(argc>1)
/*因為這3個類都定義了int型的轉換建構函式,即以int 為引數的建構函式,
所以下面可以直接賦值,而無需過載「=」運算子*/
deci=atoi(argv[1]);
octa=atoi(argv[1]);
hexa=atoi(argv[1]);
//分別以3種格式將數字輸出
displaynumber( &deci); //將呼叫cdispdecimal類定義的虛函式displayformat()
displaynumber( &octa); //將呼叫cdispoctal類過載的虛函式displayformat()
displaynumber( &hexa); //將呼叫cdisphexadecimal類過載的虛函式displayformat()
return 0;
命令列c121.exe 50
的輸出結果:
the decimal value is 50
the octal value is 62
the hexadecimal value is 32
上例定義的3個類分別封裝了一種功能的實現,如果需要增加二進位制格式的輸出,只需再定義乙個派生類cdispbinary,不必改寫通用函式displaynumber()。
通過上例對虛函式的討論,我們對它的應用價值已經有了乙個深刻的認識。正如指標是c語言的靈魂,虛函式是c++的靈魂。mfc微軟基礎類庫廣泛應用了虛函式,增強其通用性。於是,利用mfc文件/檢視框架,就可以編寫出多種功能的應用程式。
靜態聯編與動態聯編
在c 中,多型性主要是通過函式過載實現的。過載函式是指程式中對同名函式進行呼叫時,編譯器會根據函式引數的型別和個數,決定該呼叫哪一段函式 來處理這個函式呼叫。這種把函式呼叫與適當的函式 相對應的動作,叫做聯編。聯編分為靜態聯編和動態聯編。在編譯階段決定執行哪個同名的被呼叫函式,稱為靜態聯編。在編譯階...
動態聯編與靜態聯編
首先,聯編是指乙個電腦程式的不同部分彼此關聯的過程。靜態聯編是指聯編工作在編譯階段完成的,這種聯編過程是在程式執行之前完成的,又稱為早期聯編。要實現靜態聯編,在編譯階段就必須確定程式中的操作呼叫 如函式呼叫 與執行該操作 間的關係,確定這種關係稱為束定,在編譯時的束定稱為靜態束定。靜態聯編對函式的選...
動態聯編與靜態聯編
include using namespace std class base 輸出為 f1 of base destructor base f1 of derive destructor base 說明 只要將基類的函式設定為虛函式,那麼所有覆蓋它的子類的函式也都是虛函式,而不需要再使用virtua...