1. 非虚函数在编译时根据对象、绑定到对象的指针或引用的类型来确定所调用的函数版本。比如在基类A中定义public函数f,类B定义为基类A的派生类。再定义函数g,其形参为A的引用,函数内部调用函数f。那么即使传递给函数g的实参是类B的对象,调用的函数f也是基类A中的版本。这与虚函数的动态绑定机制是不同的。
2. 虚函数可以有默认实参,具体的值是在编译时确定的,因而与对象的动态类型无关。通过基类的引用或指针调用虚函数则实参采用的是基类中定义的版本,通过派生 类的引用或指针调用虚函数采用的则是派生类中定义的默认实参版本。因此,应该避免同一虚函数在基类和派生类中具有不同的默认实参列表。
3. protected和private继承时可以采用using声明恢复个别成员的访问权限,但访问权限必须与基类中的级别相同,不能更加宽松或者严格。
4. class保留字定义的派生类默认为priavte继承,而struct保留字定义的派生类默认public继承。两个保留字class和struct在定义类时,除了默认的成员访问权限和默认的派生访问权限不同外,没有其它区别。
5. 友元关系不能继承。基类的友元不具有访问派生类的特殊权限;被赋予友元的基类具有访问授予它友元关系的类的特权,但是从基类派生的类则不具备此权限。
6. 基类定义的static成员在整个继承层次中只有一个实例,它可以通过基类访问,也可以通过派生类访问。
7. 当将基类的引用或指针绑定到派生类的对象时,会发生从派生类到基类的转换,即派生类的衍生部分会被“切掉”。这样的转换是否具有权限,判断标准是看基类的public成员能否访问。需要注意的是,可以将派生类的对象来初始化或赋值给基类对象,但并不存在从派生类对象到基类对象的自动转换,这和引用或指针下的情形有所区别。
另一方面,从基类到派生类的转换是完全不存在的。
Derived d;
Base *pb = d;
Derived *pd = pb;
有时候像上面这样的特定转换实际上是安全的,然而该程序段无法通过编译,因为编译器只检查变量的静态类型是否匹配。这里可以采用static_cast来强制类型转换或者用dynamic_cast将检查推迟到程序运行时。
8. 派生类只能在初始化列表中包含直接基类的构造函数,不能“越级”调用,以尊重基类接口。
9. 根据类设计的基本法则,如果一个类需要自定义析构函数,那么它必然需要其它的复制控制成员。这一法则在引入继承概念后有一个例外——基类可能为了将析构函数设为虚函数而具有空析构函数,即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
10. 在构造函数和析构函数中调用虚函数时虚函数总是自身类型定义的版本。
11. 派生类中若出现与所继承基类中同名的成员函数,即使参数列表不相同,基类中的同名函数也会被覆盖掉。与此类似,同名基类数据成员会被隐藏。因此,如果派生类重新定义了重载成员,则通过派生类只能访问重新定义的那些版本。而如果派生类想使用基类中的所有版本,要么一个也不定义,要么全部重新定义(not or all)。想访问基类中的一部分版本而其它部分保持不变可通过using引入基类中的名字(不能包括函数参数列表),然后重新定义想重新定义的重载函数。
确定函数调用步骤如下:
1)确定进行函数调用的对象、引用或指针的静态类型。
2)在该类中查找函数,如果找不到就在直接基类中查找,如此沿着继承链向上搜索,找不到则报错。
3)找到后进行常规类型检查,不合法则报错。
4)如果是虚函数且通过指针或引用调用,编译器生成代码根据对象的动态类型来决定调用函数的哪个版本。