C++11 @5
类的派生和继承
Java 中虽然没有类的多重继承,但一个类可以实现多个接口,这其实也算是多重继承了。相比 Java 的这种设计,C++ 中类的多重继承太过灵活,使用时需要特别小心,否则菱形继承的问题很难避免。
class Derived : private Base, public VirtualBase {
// 注意,如果没有指定派生方式的话,默认为 private 方式
}
虚函数、纯虚函数和虚析构函数
Java 语言里,多态
是借助派生类重写(override
)基类的函数来表达,而 抽象
则是借助抽象类(包括抽象方法)或者接口来实现。而在 C++ 中,虚函数
和 纯虚函数
就是用于描述 多态
和 抽象
的利器:
- 虚函数:基类定义虚函数,派生类可以重写(override)它。
当我们拥有一个派生类对象(通过基类引用类型或者基类指针类型的变量实例化)来调用该对象的虚函数时,被调用的虚函数是派生类重写过的虚函数(如果该虚函数被派生类重写了的话)。
对于上面的情况,Java 是怎么样的?
- 纯虚函数:拥有纯虚函数的类不能实例化。从这一点看,它和 Java 的抽象类和接口非常类似。
//虚函数由 virtual 标示
virtual void getResult(bool isHardCode) {
cout << "Happy new year" << endl;
}
//纯虚函数由"virtual"和"=0"同时标示
virtual void doAction(int x, int y) = 0;
Tips:
-
派生类重写虚函数时候最好添加
override
标识,这样编译器能做一些额外检查而能提前发现一些错误。 -
虚函数被
override
的时候,基类和派生类声明的虚函数在函数名
,参数
等信息上需保持一致。 -
对析构函数而言,由于析构函数的函数名必须是
~类名
,所以派生类
和基类
的析构函数名
肯定是不同的。 -
通过基类指针来删除派生类对象时,是派生类对象的析构函数被调用。所以,当基类中如果有
虚函数
的时候,一定要记得将其析构函数
变成虚析构函数
。(注意,析构函数也是函数,和普通函数没什么区别)
virtual ~ Father() {
cout << "Init Father::~ Father()" << endl;
}
Father *father = new Son("WWE", 25);
...
//由于 Father 的析构函数是虚函数,所以 Son 的析构函数被调用
delete father;
// 这样就会直接调用基类的析构函数
// 运行时,~ Son()将先被调用。
father->~ Father()
在 C++ 中,也可以阻止某个虚函数被 override
,方法和 Java 类似,就是在函数声明后添加 final
关键词。
// test1 将不能被派生类 override 了
virtual void test(boolean test) final;
小结:
-
如果想实现多态,就在基类中为需要多态的函数增加
virtual
关键词; -
如果基类中有虚函数,也请同时为基类的析构函数添加
virtual
关键词。只有这样,指向派生类对象的基类指针变量被delete
时,派生类的析构函数才能被调用。
构造和析构函数的调用次序
-
对构造函数而言,基类的构造函数 先于 派生类构造函数被调用;
-
如果派生类有多个基类,则按照在派生列表里的顺序调用各自的构造函数。
// 比如 Derived 派生列表中基类的顺序是:先 Base,然后是 VirtualBase。
// 所以 Base 的构造函数先于 VirtualBase 调用,最后才是 Derived 的构造函数。
class Derived : private Base, public VirtualBase {
}
-
析构函数则是相反的过程,即派生类析构函数先被调用,然后再调用基类的析构函数。
-
如果是多重继承的话,则按照它们在派生列表里出现的相反次序调用各自的析构函数。
// Derived 类实例析构时,Derived 析构函数先调用,然后 VirtualBase 析构,最后才是 Base 的析构。
class Derived : private Base, public VirtualBase {
}