11.1.3 为什么需要虚函数

    结合一段示例代码11.1来分析虚函数的作用,以帮助读者理解使用多态的意义。

    代码11.1 没有使用虚函数带来的问题VirtualProblem


    <—————————————-文件名:example1101.cpp—————————————> 01 #include<iostream> 02 using namespace std; 03 class base//基类base定义 04 { 05 public: 06 void disp()//基类base中的普通成员函数disp 07 { 08 cout<<"hello,base"<<endl; 09 } 10 }; 11 class child1:public base//派生类child1从base派生而来 12 { 13 public: 14 void disp()//派生类child1中定义的disp函数将base类中定义的disp函数隐藏 15 { 16 cout<<"hello,child1"<<endl; 17 } 18 }; 19 class child2:public base//派生类child2从base派生而来 20 { 21 public: 22 void disp()//派生类child2中定义的disp函数同样会隐藏base类中定义的disp函数 23 { 24 cout<<"hello,child2"<<endl; 25 } 26 }; 27 void display(base*pb)//display函数,以base指针为参数 28 { 29 pb->disp(); 30 } 31 int main() 32 { 33 34 base*pBase=NULL,obj_base;//创建一个基类指针pBase,初始化为NULL,创建一个base类对象obj_base 35 obj_base. disp();//通过对象名调用disp函数 36 pBase=&obj_base;//使用obj_base的地址为pBase赋值 37 pBase->disp();//通过指针调用disp函数 38 child1*pChild1=NULL,obj_child1; 39 //创建一个child1类指针pChild1,初始化为NULL,创建一child1类对象obj_child1 40 obj_child1. disp();//通过对象名调用disp函数 41 pChild1=&obj_child1;//使用obj_child1的地址为pChild1赋值 42 pChild1->disp();//通过指针调用disp函数 43 child2*pChild2=NULL,obj_child2; 44 //创建一个child2类指针pChild2,初始化为NULL,创建一个child2类对象obj_child2 45 obj_child2. disp();//通过对象名调用disp函数 46 pChild2=&obj_child2;//使用obj_child2的地址为pChild2赋值 47 pChild2->disp();//通过指针调用disp函数 48 pBase=&obj_child1;//使用obj_child1的地址为pBase赋值 49 pBase->disp();//通过指针pBase调用disp函数 50 display(&obj_base);//函数调用 51 display(&obj_child1); 52 display(&obj_child2); 53 return 0; 54 }

    输出结果如下所示。


    hello,base hello,base hello,child1 hello,child1 hello,child2 hello,child2 hello,base hello,base hello,base hello,base

    【代码解析】代码的输出结果多少让人有些出乎意料,前6个输出操作没有问题,分别根据对象名和本类指针访问类中的成员,但第7个输出操作,代码第48~49行如下所示。


    pBase=&obj_child1; pBase->disp();

    仍旧输出了“hello,base”,而不是“hello,child1”,这说明,通过指针访问对象成员时,具体要访问哪个对象只取决于指针的类型,而与用什么对象给这个指针赋值无关。这也解释了代码11.1中3次调用display函数,全都输出了“hello,base”的原因。

    是时候引入虚函数了,其定义和相关内容将在稍后章节介绍,这里只对代码11.1进行简单的修改,在base类的disp函数前加上关键字virtual进行修饰,如下所示。


    virtual void disp() { cout<<"hello,base"<<endl; }其他保持不变,编译链接并运行后

    输出结果如下所示。


    hello,base hello,base hello,child1 hello,child1 hello,child2 hello,child2 hello,child1 hello,base hello,child1 hello,child2

    通过对象名和本类指针进行的前6个输出结果没有变化,第7个输出变为“hello,child1”,3次display函数调用也根据输入对象的不同有了不同的结果。

    从输出的结果可知,当通过指针调用virtual修饰的虚函数时,具体调用哪个版本不再取决于指针的类型,而是取决于指针所指的对象类型。

    这样做的好处在于以下两点。

    ❑在程序中只要定义了诸如代码11.1中display这样的函数,只要以基类指针为参数,就可以将派生类对象的地址作为参数,根据参数对象类型的不同,执行的操作也会不同,真正体现了“一种接口,多种方法”,省去了重复定义同功能函数的麻烦。

    ❑通过基类指针能处理所有派生类中的情况,相当于现在的代码可以服务于未来要添加的类(派生类)。