上一讲鸡啄米讲的是运算符重载为类的友元函数。今天讲的是属于包含多态的虚函数。

       一.虚函数的意义

       在讲赋值兼容规则时鸡啄米给出了一个程序例子,其中包含类Base、Child0和Child1。在程序运行结果中我们看到,main函数中Base类型的指针pBase,分别指向Base、Child0和Child1类的对象时调用的show函数都是基类Base的show函数。因为基类类型的指针指向派生类对象时,通过此指针只能访问从基类继承来的同名成员。这些在C++编程入门系列之四十三--赋值兼容规则中已经分析过了。

       但是如果我们希望通过指向派生类对象的基类指针,访问派生类中的同名成员该怎么办呢?这就要用到虚函数了。我们在基类中将某个函数声明为虚函数,就可以通过指向派生类对象的基类指针访问派生类中的同名成员了。这样使用某基类指针指向不同派生类的不同对象时,就可以发生不同的行为,也就实现了运行时的多态(编译时并不知道调用的是哪个类的成员)。

       虚函数是动态绑定的基础。记住,虚函数是非静态的成员函数,一定不能是静态(static)的成员函数。虚函数在以后我们进行软件架构设计时会起到很关键的作用。编程入门时可能不会有这方面的意识,等熟练到一定程度你就会发现虚函数的强大。

       二.一般虚函数成员的声明和使用

       一般的虚函数声明形式为:

        virtual 函数类型 函数名(形参表)
        {
                  函数体
        }

       虚函数就是在类的声明中用关键字virtual限定的成员函数。以上声明形式是成员函数的实现也在类的声明中的情况。如果成员函数的实现在类的声明外给出时,则虚函数的声明只能出现在类的成员函数声明中,而不能在成员函数实现时出现,简而言之,只能在此成员函数的声明前加virtual修饰,而不能在它的实现前加。

鸡啄米:C++编程入门系列之四十八(多态性:虚函数)

       总结下运行时多态的几个条件:1.类之间要满足赋值兼容规则;2.要声明虚函数;3.通过类的对象的指针、引用访问虚函数或者通过类的成员函数调用虚函数。下面举例说明下,大家通过这个例子来对照下这几个条件。

       此例是由赋值兼容规则中的例子改进的。将基类中的函数show声明为虚函数,程序其他部分不做任何修改。    

       #include <iostream>        
       using namespace std;
       class Base           // 基类Base的声明
       {
       public:
                 virtual void show()    { cout << "Base::show()" << endl; }      // 虚成员函数show
       };
       class Child0 : public Base     // 类Base的公有派生类Child0的声明
       {
       public:
                 void show()    { cout << "Child0::show()" << endl; }    // 虚成员函数show
       };
       class Child1 : public Child0   // 类Child0的公有派生类Child1的声明
       {
       public:
                 void show()    { cout << "Child1::show()" << endl; }    // 虚成员函数show
       };
       void CallShow(Base *pBase)     // 一般函数,参数为基类指针
       {
                pBase->show();
       }
       int main()
       {
               Base base;                 // 声明Base类的对象
               Base *pBase;             // 声明Base类的指针
               Child0 ch0;                 // 声明Child0类的对象
               Child1 ch1;                 // 声明Child1类的对象
               pBase = &base;        // 将Base类对象base的地址赋值给Base类指针pBase
               CallShow(pBase);
               pBase = &ch0;            // 将Child0类对象ch0的地址赋值给Base类指针pBase
               CallShow(pBase);
               pBase = &ch1;            // 将Child1类对象ch1的地址赋值给Base类指针pBase
               CallShow(pBase);
               return 0;
       }

       程序运行结果:

       Base::show()
       Child0::show()
       Child1::show()

       我们可以看出,仅仅是在Base类中的show函数前加了virtual的修饰,运行结果就差了很多,这正是虚函数的美丽所在。

       例程中,类Base、Child0和Child1属于同一个类族,而且Child0是由Base公有派生的,Child1是从Child0公有派生的,所以满足赋值兼容规则,这就符合了运行时多态的第一个条件。基类Base的函数show声明为了虚函数,这是第二个条件。在CallShow函数中通过对象指针pBase来访问虚函数show,这又满足了第三个条件。这个动态绑定过程在运行时完成,实现了运行时的多态。这样通过基类指针就可以访问指向的不同派生类的对象的成员,这在软件开发中不仅使代码整齐简洁,而且也大大提高了开发效率。

       基类的成员函数声明为虚函数以后,派生类中的同名函数可以加virtual修饰也可以不加。

       三.虚析构函数

       大家可能奇怪为什么不先讲虚构造函数,很简单,因为不能声明虚构造函数,而可以声明虚析构函数。

       多态是指不同的对象接收了同样的消息而导致完全不同的行为,它是针对对象而言的,虚函数是运行时多态的基础,当然也是针对对象的,而构造函数是在对象生成之前调用的,即运行构造函数时还不存在对象,那么虚构造函数也就没有意义了。

       析构函数用于在类的对象消亡时做一些清理工作,我们在基类中将析构函数声明为虚函数后,其所有派生类的析构函数也都是虚函数,使用指针引用时可以动态绑定,实现运行时多态,通过基类类型的指针就可以调用派生类的析构函数对派生类的对象做清理工作。

       前面讲过,析构函数没有返回值类型,没有参数表,所以虚析构函数的声明也比较简单,形式如下:

       virtual ~类名();

       虚函数的内容就先讲到这里了,抽象度比较高。建议大家经常回来复习,以便更熟练的掌握,并服务于以后的程序架构的设计。谢谢大家,有问题还是欢迎到鸡啄米博客来交流。