假设有一个func函数,派生类里重写了基类的里的这个函数。那么这个派生类里的这个函数只有派生类的对象才可以调用。因为一个派生类的对象作为一个基类对象的引用或者是一个基类的指针指向这个派生类,它们的行为都像一个基类对象的行为。通过这个指针来调的话,将调用基类的func函数。如果一个变量是基类的引用,引用的是一个派生类的对象,或者派生类的对象传给函数(值传递或者应用传递)函数的形参是基类类型,那么基类的func()函数将被调用
#include <iostream.h>
class Base
{
public:
virtual void func( )
{cout << "Base class function.\n";}
};
class Derived : public Base
{
public:
void func( )
{cout << "Derived class function.\n";}
};
void foo(Base& b)
{ b.func( ); }
int main( )
{
Derived d;
Base b;
Base* p = &d;
Base& br = d;
b= d; // i = j;
b.func( );
d.func( );
p-> func( );
foo(d);
foo(b);
br.func();
return 0;
}
在一些具体的应用中,一个基类的指针指向派生类的对象,它想要调用派生类中重写的父类的函数。它的行为是一个基类的行为。这种情况下,我们把这个函数声明为虚函数。使得这个指向派生类的指针能够调用派生类里重写的方法
使用虚函数,如果一个派生类的对象的引用当作基类对象的引用传参,或者是一个基类的指针(指向派生类的对象),派生类的函数将会被调起,而没有使用指针或引用的向下类型转换。
附注
如果一个派生类的对象给一个基类的对象赋值,左值不会转换成一个派生类的对象。如果派生类对象作为参数传给一个函数,值传递,参数的接受类型为基类类型。正式的,我们给它传一个基类的对象。在这种情况下,基类版本的虚函数将会被调用
如果是基类的指针或引用指向派生类,调用虚函数。派生类版本的函数将会被调用
在一个有虚函数的类里,当一个基类的指针被delete,基类的析构函数会被调用。但是,如果这个指针指向派生类的对象。那么仅仅只有基类的析构被调用,而不是通过在派生类的对象。
析构一个派生类的对象,当我们使用delete操作符来delete一个指向派生类对象的基类指针时,必须要保证派生类的析构函数被调用
这意味着基类的析构函数必须是虚函数。因此,如果你声明一个类有派生类的话,这个类的析构函数最好是虚函数。有没有必要声明一个类的析构函数是虚函数的一个规则就是,看这个类是否有子类。
#include <iostream.h>
#include <string.h>
class Thing
{
public:
virtual void what_Am_I( ) {cout << "I am a Thing.\n";}
~Thing(){cout<<"Thing destructor"<<endl;}
};
class Animal : public Thing
{
public:
virtual void what_Am_I( ) {cout << "I am an Animal.\n";}
~Animal(){cout<<"Animal destructor"<<endl;}
};
void main( )
{
Thing t ;
Animal x ;
Thing* array[2];
array[0] = &t; // base pointer
array[1] = &x;
for (int i=0;i<2; i++)
array[i]->what_Am_I() ;
return ;
}
如果对象能够动态允许我们使用delete来释放空间,并且保证能够正确的释放内存
delete 一个指向对象的指针,这将调用次对象的析构函数。但是,如果这个对象是由基类的指针指向的,那么只会调用基类的析构函数。
#include <iostream.h>
#include <string.h>
class Thing
{ public:
virtual void what_Am_I( ) {cout << "I am a Thing.\n";}
~Thing(){cout<<"Thing destructor"<<endl;}
};
class Animal : public Thing
{
public:
void what_Am_I( ) {cout << "I am an Animal.\n";}
~Animal(){cout<<"Animal destructor"<<endl;}
};
int main( )
{
Thing* t =new Thing();
Animal* x = new Animal(); // Thing* y = new Animal(); delete y;
x-> what_Am_I() ;
Thing* array[2];
array[0] = t; // base pointer
array[1] = x;
for (int i=0;i<2; i++)
array[i]->what_Am_I() ;
delete array[0];
delete array[1];
return 0;
}
如果在一个类里的函数是虚函数,或者说这个类有子类。一定要确认这个类的析构函数为虚函数。如果基类的析构函数为虚函数,那么子类的析构函数也是虚函数。
当虚函数被一个基类对象的引用调用时,或者被一个基类指针调用时,它的行为依靠对象的真实身份。这在运行时才能确定的。这就叫做动态绑定
动态绑定使得程序有这样一种能力,注意这些代码,在将来的某个时刻被加载。设想一下,在一段程序里,一个虚函数被基类的引用调用。根据对象的真实身份,基类的还是派生类的,类里的成员函数会被调用。定义一个派生类,在派生类里重写虚函数。在新的派生类里定义这个对象的引用。如果是这样的话,新的派生类的函数将会被调用。而不用修改原有的代码。