虚函数和纯虚函数
Define static type and dynamic type.
-
static type: Type with which a variable is defined or that an expression yields. Static type is known at compile time.
-
dynamic type: Type of an object at run time. The dynamic type of an object to which a reference refers or to which a pointer points may differ from the static type of the reference or pointer.
虚函数
所有的虚函数都必须要有定义,因为我们直到运行时才知道到底调用了哪个版本的虚函数.
当某个虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定是哪个版本,被调用的函数与绑定到指针或引用上的对象的动态类型相匹配.
Quote base("0-201-123", 59);
print_total(cout, base, 10); // 调用Quote::net_price
Bulk_quote derived("132", 50, 5, .19);
print_total(cout, derived, 10); // 调用Bulk_quote::net_price
动态绑定只有当我们通过指针或引用调用虚函数才会发生.
base = derived; // 把derived的Quote部分拷贝给base
base.net_price(20); // 调用Quote::net_price
没有发生动态绑定.
派生类中的虚函数
一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数.
一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参列表必须与被它覆盖的基类函数完全一致.
同样,派生类中虚函数的返回类型也必须与基函数匹配.但是存在一个例外,当类的虚函数返回类型是类本身的指针或引用时,上述规则无效,也就是说,如果D
继承B
,则B
的虚函数可以返回*B
而派生类的对应函数可以返回*D
,这要求从D
到B
的类型转换是可以访问的.
final
和override
说明符
派生类如果定义了一个函数与基类中虚函数的名字相同但形参列表不同,仍然合法.但编译器认为新定义的这个函数与基类中原有的函数是相互独立的.这时,派生类的函数并没有覆盖掉基类的版本.
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1 : B {
void f1(int) const override; // 正确:f1与基类中f1匹配
void f2(int) override; // 错误:B没有形如f2(int)的函数
void f3() override; // 错误:f3不是虚函数
void f4() override; // 错误:B没有名为f4的函数
};
我们还能把某个函数指定final
,如果我们已经把函数定义为final
了,之后任何尝试覆盖该函数的操作都会引发错误:
struct D2 : B {
// 从B继承f2()和f3(), 覆盖f1(int)
void f1(int) const final; // 不允许后续的其他类覆盖f1(int)
};
struct D3 : D2 {
void f2(); // 正确:覆盖从间接基类B继承而来的f2
void f1(int) const; // 错误:D2已经将f2声明为final
};
final
和override
说明符出现在形参列表(包括任何const
或引用修饰符)以及尾置返回类型之后.
虚函数与默认实参
虚函数可以有默认实参,如果虚函数使用了默认实参,则该实参值由本次调用的静态类型决定.
如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类的函数版本.
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致
回避虚函数
有时候我们希望虚函数的调用不要进行动态绑定,而是强迫其执行某个版本.使用作用域运算符.
double undiscounted = baseP->Quote::net_price(42);
该代码强行调用Quote
的net_price
函数,而不管baseP
实际指向的类型.
如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无递归.
纯虚函数
纯虚函数没有实际意义,和普通虚函数不一样,一个纯虚函数不用定义,我们通过在函数体的位置书写=0
就可以将一个虚函数说明为纯虚函数,其中=0
只能出现在类内部的虚函数声明语句处.
含有纯虚函数的类是抽象基类
含有(或未经覆盖直接继承)纯虚函数的类是抽象基类.抽象基类类负责定义接口,后续的类可以覆盖该接口,我们不能(直接)创建一个抽象基类.
我们不能创建抽象基类的对象.