如果父类虚函数没有被覆盖,则这个虚函数是完全没有意义的。
如有以下两个类的继承情况:
class Base{
public:
virtual void f(){ cout<<"Base::f()" <<endl; }
virtual void g(){ cout<<"Base::g()" <<endl; }
virtual void h(){ cout<<"Base::h()" <<endl; }
void m(){ cout << "Base::m()" << endl; }
};
class Derived:public Base{
public:
void f(){ cout << "Derived::f()"<< endl; }
void g(){ cout << "Derived::g()"<< endl; }
void m(){ cout << "Derived::m()" << endl; }
void n(){ cout << "Derived::n()"<< endl; }
};
第一种情况:我们使用虚函数一般是
Base*p=newDerived;
p->f(); // Derived::f()
p->g(); // Derived::g()
p->h(); // Base::h()
p->m(); // Base::m()
p->n();// 编译报错
这是因为:f和g都是虚函数,并且在Derived的对象中都已经将这个函数覆盖了(注意要想覆盖必须是除了函数体定义不同其他的完全相同,否则就不能覆盖而视为不同的函数了);h是虚函数但是没有被覆盖;而n只是Derived类对象的成员函数而不是Base类的成员函数。
第一行代码的意义就是:声明一个对象,并把这个对象的指针强制转换成一个Base类型的,我们知道在创建Derived类对象的时候,其中必然包含了Base基类中的所有内容,在进行指针强制转换后,就只会在继承的Base区域内寻找需要执行的对象或者需要操作的数据。根据上面所描述的,f和g被覆盖了,所以调用的时候就会调用Derived的,h没有被覆盖所以调用Base的h,而n根本就不在这个区域内所以会编译报错:找不到这个成员。
第二种情况:
Derived*p=newDerived;
p->f(); // Derived::f()
p->g(); // Derived::g()
p->h(); // Base::h()
p->m(); // Derived::m()
p->n();// Derived::n()
这个时候并没有把Derived类对象的指针做强制转换,这个时候就会在Derived的区域内寻找数据和函数进行执行;这个时候实际上在Derived对象的体内,因为f和g被覆盖了,所以只含有一个f和g而且是Derived的f和g;h没有被覆盖但是h是Base的h;总之对于虚函数的调用是通过查虚函数表来实现的。m在Base和Derived中都有定义但是不是虚函数,所以没有覆盖在这里也不是重载,而是属于两个不同类的函数,在具体调用的时候首先是根据函数名称即标识符来查看调用哪个类中的m函数,这个查找过程是是又一定的名字查找规则的,尤其是在多重继承中体现更明显,在C++编程思想第二卷P374中有具体查找规则描述;确定类了之后再选择函数具体的重载形式,比如如果在Base中m还有一个参数为int的重载形式,而在Derived中没有,但是在调用的时候使用p->m(1)调用,在这个例子的情形下就会报错,因为根据名称查找规则确定是使用Derived类中的m函数,但是这个类中没有参数为int的重载形式,如果这在Derived中重载了这种形式则肯定没有问题;同样在Derived类中如果不定义m则也没有问题,因为这个时候在名称查找的时候就会确定的是使用Base类中的m函数,而在Base类中恰好有m的int参数的重载形式,所以就不会报错,只是这个时候就会调用Base类中的m函数了。
综上两种情况描述了目前我所见到的类继承中的两大类情形:一种就是虚函数的调用机制;另一种就是同名函数的查找和调用机制。