理解C++中继承层次的关键在于理解如何确定函数调用,确定函数调用遵循以下四个步骤:
(1)首先确定进行函数调用的对象,引用或指针的静态类型。
(2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。
(3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。
(4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。
1 class Base{ 2 public: 3 virtual int fcn(); 4 } 5 6 7 class D1: public Base{ 8 public: 9 //hides fcn in the base; this fcn is not virtual 10 int fcn(int); //nonvirtual function hides D1::fcn(int) 11 //D1 inherits definition of Base::fcn() 12 } 13 14 class D2 : public D1{ 15 public: 16 int fcn(int); //nonvirtual function hides D1::fcn(int) 17 int fcn(); //redefines virtual fcn from Base 18 } 19 20 D1中的fcn版本没有重定义Base的虚函数fcn,相反,它屏蔽了基类的fcn。 21 结果D1有两个名为fcn的函数:1、类从Base继承了一个名为fcn的虚函数, 22 2、类又定义了自己的名为fcn的非虚成员函数,该函数接受了一个int的 23 形参。但是,从Base继承的虚函数不能通过D1对象(或D1的引用或指针) 24 调用,因为该函数被fcn(int)的定义屏蔽了。 25 26 类D2重定义了它继承了两个函数,它重定义了Base中定义的fcn的原始版本 27 并重定义了D1中定义的非虚版本; 28 //通过基类调用被屏蔽的虚函数 29 //通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类: 30 Base bobj; D1 d1obj; D2 d2obj; 31 Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj; 32 bp1->fcn(); //ok:virtual call, call Base::fcn at run time 33 bp2->fcn(); //ok:virtual call, call Base::fcn at run time 34 bp3->fcn(); //ok:virtual call, call D2::fcn at run time 35 36 三个指针都是基类类型的指针,因此通过在Base中查找fcn来确定这三个调用, 37 所以这些调用是合法的;另外,因为fcn是虚函数,所以编译器会生成代码, 38 在运行时基于引用或指针所绑定的对象的实际类型进行调用。在bp2的情况, 39 基本对象是D1类的,D1类没有重定义不接受实参的虚函数版本,通过bp2的函数 40 调用(在运行时)调用Base中的定义的版本;