虚函数
当我们使用基类的指针或引用调用一个虚函数成员时,会执行动态绑定。因为我们直到运行时才知道调用的哪个虚函数,所以需要为所有虚函数都定义。通常,如果我们不使用某个函数,则无需为其定义,但是虚函数必须要定义,因为不知道会调用哪个虚函数,所以都要准备。
1.对虚函数的调用可能在运行时才被解析
当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个。如:
Quote base("abc", 50);
printTotal(cout, base, 10); //调用Quote::netPrice
BulkQuote bulk("abcd", 50, 5, 19);
printTotal(cout, bulk, 10); //调用BulkQuote::netPrice
动态绑定只有**当我们通过指针或引用调用虚函数时才会发生。**如:
base.netPrice(20); //调用Quote::netPrice
bulk.netPrice(20); //调用BulkQuote::netPrice
当我们通过具有普通类型(非引用非指针)的表达式调用虚函数时,在编译器就会将调用的版本确定下来。
2.final和override说明符
①override说明符
派生类如果定义了一个函数与基类中的虚函数的名字相同但是形参列表不同,仍然合法,编译器将认为新定义的这个函数与基类中原有的函数是相互独立的。这时,派生类的函数并没有覆盖掉基类中的版本。这种声明往往意味着错误,但是我们很难发现。
我们可以使用override关键字来说明派生类中的虚函数,这么做的好处是程序员的意图更加清晰,防止派生类的虚函数没有覆盖掉基类中的虚函数,如果使用override标记某个函数,但没有覆盖已存在的虚函数,此时会报错。 如:
class A {
public:
virtual void f1(int) const;
virtual void f2();
};
class B : public A {
public:
void f1(int) const override; //正确:f1与基类中的f1相匹配
void f2(int) override; //错误:B没有形如f2(int)的虚函数
};
②final说明符
我们可以把某个函数指定为final,如果我们已经把函数定义为final,则之后任何尝试覆盖该函数的参数都将发生错误。如:
class C : public B {
public:
void f1(int) const final; //不允许后续的其他子类覆盖f1(int)
};
class D : public C {
public:
void f1(int) const; //错误:D2已经将f2声明成final
};
③回避函数的机制
我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本,使用作用域运算符可以实现这一目的。如:
Quote *basep;
double ret = basep->Quote::netPrice(42); //强制调用Quote的netPrice函数,而不管baseP实际指向的对象是什么类型