9.6.1 虚函数的概念
虚函数是以virtual关键字声明的基类函数。如果在基类中将某个函数指定为virtual,并且派生类中有另外一个该函数的定义,则编译器将知道我们不想静态连接该函数。我们真正需要的是基于调用该函数的对象种类,在程序的特定位置选择调用哪一个函数。
试一试:修正CGlassBox类
要使本示例像原来希望的那样工作,我们只需在两个类中给Volume()函数的定义添加virtual关键字即可。我们可以在新项目Ex9_07中进行试验。下面是CBox类的定义:
- // Box.h in Ex9_07
- #pragma once
- #include <iostream>
- using std::cout;
- using std::endl;
-
- class CBox // Base class
- {
- public:
- // Function to show the volume of an object
- void ShowVolume() const
- {
- cout << endl
- << "CBox usable volume is " << Volume();
- }
-
- // Function to calculate the volume of a CBox object
- virtual double Volume() const
- { return m_Length*m_Width*m_Height; }
-
- // Constructor
- CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0)
- :m_Length(lv), m_Width(wv), m_Height(hv) {}
- protected:
- double m_Length;
- double m_Width;
- double m_Height;
- };
GlassBox.h头文件的内容应该如下所示:
- // GlassBox.h in Ex9_07
- #pragma once
- #include "Box.h"
-
- class CGlassBox: public CBox // Derived class
- {
- public:
- // Function to calculate volume of a CGlassBox
- // allowing 15% for packing
- virtual double Volume() const
- { return 0.85*m_Length*m_Width*m_Height; }
-
- // Constructor
- CGlassBox(double lv, double wv, double hv): CBox(lv, wv, hv){}
- };
Ex9_07.cpp文件中的main()函数与上一个示例中的相同:
- // Ex9_07.cpp (the same as Ex9_06.cpp)
- // Using a virtual function
- #include <iostream>
- #include "GlassBox.h" // For CBox and CGlassBox
- using std::cout;
- using std::endl;
-
- int main()
- {
- CBox myBox(2.0, 3.0, 4.0); // Declare a base box
- CGlassBox myGlassBox(2.0, 3.0, 4.0);// Declare derived box - same size
-
- myBox.ShowVolume(); // Display volume of base box
- myGlassBox.ShowVolume(); // Display volume of derived box
-
- cout << endl;
- return 0;
- }
示例说明
如果运行这个只是给Volume()定义添加了virtual关键字的程序,那么将得到下面的输出:
- CBox usable volume is 24
- CBox usable volume is 20.4
现在,该程序无疑做了我们原来希望它做的事情。第一次用CBox对象myBox调用ShowVolume()函数时,该函数调用了CBox类的Volume()版本。第二次用CGlassBox对象myGlassBox调用ShowVolume()函数时,该函数调用了派生类中定义的Volume()版本。
注意,虽然我们在派生类的Volume()函数定义中使用了virtual关键字,但这样做不是必需的。在基类中将该函数定义为virtual已经足够了。不过,笔者建议您在派生类中为虚函数指定virtual关键字,因为这样可以使阅读派生类定义的任何人都清楚地知道这些函数是动态选择的虚函数。
要使某个函数表现出虚函数的行为,该函数在任何派生类中都必须有与基类函数相同的名称、形参列表和返回类型。如果基类函数是const,那么派生类中的函数也必须是const。如果我们企图使用不同的形参或返回类型,或者将一个声明为const,而另一个不然,则虚函数机制将不能工作。函数的行为将在编译时通过静态连接确定。
虚函数的运用是一种特别强大的机制。我们或许听说过与面向对象编程(www.cppentry.com)有关的术语多态性,该术语指的就是虚函数功能。多态的某种事物能够以不同的外观出现,比如狼人、Jekyll医生或选举前后的政治家。根据当前对象的种类,调用虚函数将产生不同的结果。
注意:
从派生类函数的观点来看,CGlassBox派生类中的Volume()函数实际上隐藏了该函数的基类版本。如果我们希望从某个派生类函数中调用基类的Volume()版本,则需要以CBox::Volume()这样的形式使用作用域解析运算符来引用基类函数。