|
虚函数(虚方法)是为了满足“多态”而提出的。多态,即具有多种形态,是一种接口多种方法的体现。比如说,手机都要具备音量调节这种接口,但是不同的手机对该接口的内部实现(方法)不同,如一般的手机(基类)要求具备物理上下键的调节,而现代智能手机(派生类)还需要具备通过点击触摸屏来调节。
声明虚方法需要在方法前面使用 virtual 关键字来声明。
对于通过引用或指针来使用方法,有两种情况:
如果没有使用关键字 virtual 来声明,那么就是一般的方法,程序将根据引用类型或指针类型来选择方法。比较下面代码:- BaseClass ordinary("Window", 1000, 2000);
- DerivedClass VIP("VIP Window", 3000, 5000);
- BaseClass &b1_ref = Ordinary;
- BaseClass &b2_ref = VIP;
- b1_ref.View_Account();
- b2_ref.View_Account();
复制代码 其中,BaseClass 是基类;DerivedClass 是 BaseClass 的派生类;View_Account() 是方法。
上面,View_Account() 这个方法在基类和派生类中都有。b1_ref 和 b2_ref 都是基类的引用,虽然派生类的对象 VIP 赋给了 b2_ref ,但当 b2_ref 调用 View_Account() 这个方法时,仍然调用的是基类中的方法,而非派生类中的方法。至于为什么,可参考《派生类与基类之间的几个特殊关系》。上面代码中使用的是引用,如果换做指针,行为也类似。
如果上面代码中的 View_Account() 是虚的,那么行为将不同,那么 b2_ref.View_Account(); 这条语句使用的是 DerivedClass 中的方法,而不是 BaseClass 中的方法。同样,将引用换做指针,那么行为与之类似。
注意,如果要在派生类中重新定义基类的方法,通常应该将基类方法声明为虚的。这样,正如上所示,程序将根据对象类型而不是引用或指针的类型来选择方法的版本。另外,为基类声明一个虚析构函数也是一种惯例。
除非类不用做基类,否则的话就应当将析构函数声明为虚函数。比如,雇员 Employee 是基类,歌手 Singer 是派生类,并添加一个 char * 成员,该成员指向由 new 分配的内存。那么当 Singer 对象过期时,应当调用 ~Singer() 析构函数来释放内存。
现在看下面的代码:- Employee *pe = new Singer; // 合法,基类指针可以指向派生类对象
- ... ...
- delete pe; // 调用 ~Employee() 还是 ~Singer() ?
复制代码 如果不使用 virtual 将析构函数声明为虚析构函数的话,那么 delete 语句将调用的是 ~Employee() 析构函数。这可以释放由 Singer 对象中的 Employyee 部分指向的内存,但不会释放新的类成员指向的内存(如上的 char *)。但是,如果析构函数是虚的,那么上述代码将线调用 ~Singer 析构函数,释放由 Singer() 组件指向的内存;然后在调用 ~Employee() 析构函数来释放由 Employee 组件指向的内存。这样一来,问题就没有了。
事实上,即使不是基类,你将析构函数定义为虚的,也不会有错,但会影响效率。通常,应该给基类提供一个虚析构函数,即使它并不需要。
另外,构造函数不能是虚函数。在创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后派生类的构造函数将使用基类的一个构造函数,但这种调用顺序并不是继承机制。因此,派生类不继承基类的构造函数,所以将类构造函数声明为虚的没什么意义。
|
|