首先,访问类中的成员函数有以下几种方式:
- 通过基类和派生类的对象名直接访问
- 通过指向基类对象的基类指针和指向派生类对象的派生类指针访问
- 通过指向派生类对象的基类指针访问(不存在指向基类的派生类指针)
下面分别总结一下
1. 通过对象名访问¶
此时和 virtual 关键字没有关系
基类无需多言,派生类 主要看同名函数在派生类中有没有定义
1. 有定义¶
那么和基类中的同名函数就没有关系了,则只使用派生类中的函数(即使需要进行类型转换,如无法转换则编译报错,也不会使用基类中的函数),派生类内部也可以进行重载,甚至可以手动实现一个和基类一模一样的同名函数
2. 没有定义¶
使用基类中的函数
2. 通过正确类型的指针访问¶
此时和 virtual 关键字也没有关系此种情形和通过对象名访问无异
3. 通过指向派生类对象的基类指针访问¶
只有在此种情况下 virtual 关键字才起作用
1. 基类函数有 virtual 关键字¶
派生类中的同名同参数函数可以对基类函数进行覆盖,对于这些函数则使用派生类中的定义(如果有的话),即使派生类中可能存在其他更符合参数类型的重载,也只使用派生类中和基类参数类型一致的那个覆盖函数
2. 基类函数没有 virtual 关键字¶
无论派生类中的情况如何都没有关系,因为指向派生类的基类指针只能访问基类的成员函数以及其中带有 virtual 关键字的成员函数在派生类中的实现(覆盖)
注:¶
- virtual 关键字只在通过指向派生类对象的基类指针调用函数时起作用,这是 virtual 关键字唯一的用武之地
- virtual 关键字对类本身不会起什么作用,主要是决定了其子类中的同名同参数函数能否对基类函数进行覆盖
- 高质量C++/C 编程指南 中指出的类继承中所谓的隐藏规则大致是正确的,只是没有正确理解 virtual 关键字的作用情况,并区分对派生类的调用方式
验证代码:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f(float x) { cout << "Base::f(float) " << x << endl; }
virtual void g(float x) { cout << "Base::g(float) " << x << endl; }
void h(float x) { cout << "Base::h(float) " << x << endl; }
void j(float x) { cout << "Base::j(float) " << x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x) { cout << "Derived::f(float) " << x << endl; }
virtual void g(float x) { cout << "Derived::g(float) " << x << endl; }
virtual void g(int x) { cout << "Derived::g(int) " << x << endl; }
void h(float x) { cout << "Derived::h(float) " << x << endl; }
void j(int x) { cout << "Derived::j(int) " << x << endl; }
void k(int x) { cout << "Derived::k(int) " << x << endl; }
};
void main(void)
{
Base b;
Derived d;
Base* pb = &b;
Derived* pd = &d;
Base *ppb = &d;
b.f(3.14f);
d.f(3.14f);
pb->f(3.14f);
pd->f(3.14f);
ppb->f(3.14f);
cout << endl;
b.g(3.14f);
d.g(3.14f);
pb->g(3.14f);
pd->g(3.14f);
ppb->g(3);
cout << endl;
b.h(3.14f);
d.h(3.14f);
pb->h(3.14f);
pd->h(3.14f);
ppb->h(3.14f);
cout << endl;
b.j(3.14f);
d.j(3.14f);
pb->j(3.14f);
pd->j(3.14f);
ppb->j(3.14f);
cout << endl;
cin.get();
}
另外虚析构函数的问题需要注意下