虚拟函数-1、静态联编与动态联编,引入虚函数

在实际开发工作中,为提高代码的重用性,编写通用的功能模块,往往需要设计处理几种不同对象的通用程序,如示例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){Number=i;}

         CDispDecimal(){Number=0;}

         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){Number=i;}

         CDispOctal(){Number=0;}

         virtual DisplayFormat()

         {

         printf("The octal value is %o\n",Number);

         }

};      

 

class CDispHexadecimal: public CDispDecimal

{

public :

         CDispHexadecimal(int i){Number=i;}

         CDispHexadecimal(){Number=0;}

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文档/视图框架,就可以编写出多种功能的应用程序。

posted on 2011-05-09 17:55  carekee  阅读(507)  评论(0编辑  收藏  举报