在一个派生类之中重载基类的虚函数是函数重载的一种特殊形式,一般的函数重载只要求他们的函数名字相同,但是虚函数的重载可不仅仅需要函数名的一样,它还要求返回类型,参数个数,参数类型和顺寻全都一模一样。要不然,就显示输出错误。为啥会这么严厉?他的主要原因是,类层次重载的各个虚函数,表面上他们的的类型一样,但是他们的this指针类型不同。C++的虚特性仅负责程序运行时把基类this的指针关联类型转化成当前指向对象的类类型,却不能改变函数的其他特性。简单来说,就是他的的this只能干一件事,就是把它转化成当前指向对象的类类型,其他的多不能干。
#include<iostream>
using namespace std;
class A
{
public:
virtual void vf1()//构造虚函数
{
cout<<"it is a virtual function vf1() of A.\n";
}
virtual void vf2()
{cout<<"it is a virtual function vf2() of A.\n";}
virtual void vf3()
{cout<<"it is a virtual function vf3() of A.\n";}
void fun()
{cout<<"it is a common number function A::fun().\n";}
};
class B:public A
{
public:
void vf1()//为虚函数,但是virtual被隐藏
{cout<<"it is a virtual function vf1() of B.\n";}
void vf2(int x)
{cout<<"B::vf2()lose virtual character.the paraneter is"<<x<<"\n";}
void fun()
{cout<<"it is common over loading member function B::fun().\n";}
};
int main()
{
B b;
A *a=&b;
a->vf1();
a->vf2();
b.vf2(5);
a->vf3();
a->fun();
b.fun();
}
结果
it is a virtual function vf1() of B
it is a virtual function vf2() of A.
B::vf2()lose virtual character.the paraneter is 5
it is a virtual function vf3() of A.
it is a common number function A::fun()
it is common over loading member function B::fun()
接下来,我们进行简单的解说,这个代码段有一个很好的对比,就是void vf1()和 void vf2。函数 void vf1()在j基类和派生类之中都有有。一模一样的。并且用virtual来进行定义为虚函数。根据上文讲解虚函数的特点,基类和派生类的 void vf1都被定义为虚函数。所以a->vf1();的结果输出为派生类的结果,it is a virtual function vf1() of B。(a可是获得了b的地址)。而vf2()虽然派生类和基类都有,他们两个同名,但是函数的原型不同,所以失去了虚类的特性。导致a->vf2();输出的结果为it is a virtual function vf2() of A.,而不是B::vf2()lose virtual character.the paraneter is 5。至于fun,在派生类和基类之中都有定义,函数的原型都相同,但他是非虚的。没有动态特性。非虚函数的的调用解释依赖于指针类型,引用类型,或者依赖于对象,指针的显示指示。所以a->fun();是调用基类。b.fun();调用派生类。
虚析构函数
首先我们先说明一下构造函数为什么不能用”虚“的,构造函数在运行调用的时候它是有顺序的,并且必须按照顺序来运行,不能”选择性“的调用,缺乏自由度。所以虚构造函数是没有意义的,如果你这么干,会显示语法错误。
但是构造函数是可以虚的。它用于动态建立对象,指引delete运算符选择正确的析构调用。
下面先用一个例子看普通的构造函数删除动态派生类的的调用情况
#include<iostream>
using namespace std;
class A
{
public:
~A()
{
cout<<"A::~A is called\n";
}
};
class B:public A
{
public:
~B()
{cout<<"B::~B()is called\n";}
};
int main()
{
A*a=new B;//用基类指针建立派生类的动态对象
B*b2=new B;//用派生类建立派生类的动态对象
cout<<"delete first object:\n";
delete a;
a=NULL;
cout<<"delete second object:\n";
delete b2;
b2=NULL;
}
结果
delete first object
A::~A is called
delete second object:
B::~B()is called
A::~A is called
可见,通过基类指针建立的派生类动态对象释放的时候,它就只是释放了基类的占用的资源。派生类的占有资源却无法释放。而派生类建立的派生类的动态对象释放的时候可以看到它不仅仅释放派生类占的资源,它还释放调用基类的资源。可见,动态的分配的类层次之中存在一个问题,如果基类指针指向new运算建立的派生类的对象。而delete 运算显示的作用于指向派生类对象的指针。那么,不管,基类指针所指的对象是谁,也不管函数名是不是一样,系统至调用基类的析构函数。
这个问题只有一个解决方法。将基类析构函数说明为虚析构函数,将会让所有的析构函数自动成为虚析构函数。
让我们对上面的函数进行改动吧!过程很复杂的,特别特别复杂,很难改动的。
就是直接在 ~A()的前面加一个virtual。然后问题就解决了。
咳咳,从程序运行的结果来看,定义了基类的虚析构函数之后,基类指针指向的派生类的动态函数对象也可以用delete 析构。为基类提供一个虚析构函数,能够使派生类对象在不同状态下正确调用析构函数。所以,将基类的析构函数定义为虚的,百利无一害。