虚函数
对虚函数的调用可能在运行时才被解析
当某个虚函数通过指针或者引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数,被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那个。
必须注意的是,动态绑定只有当我们通过指针或引用调用虚函数时才会发生。如果通过普通的类型(非引用非指针)的表达式调用虚函数时,编译器就会将调用的版本确定下来。
派生类中的虚函数
一旦某个函数被声明成虚函数,则在所有的派生类中它都是虚函数,在派生类中覆盖一个虚函数时,可以再次使用 virtual
关键字指出该函数的性质,也可以不指明。
一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与它覆盖的基类函数完全一致。派生类中虚函数的返回类型也应该与基类函数匹配,该规则存在一个例外:类的虚函数返回类型是本身的指针或引用。
final 和 override 说明符
C++ 11 新标准中可以使用 override
关键字来说明派生类中的虚函数,如果使用 override
标记某个函数,该函数没有覆盖已存在的虚函数,此时编译器将报错。
还可以将某个函数指定为 final
,如果函数被定义成 final
了,则之后任何尝试覆盖该函数的操作都将引发错误。
final
和 override
关键字应该出现在形参列表(包括任何const
或引用修饰符)以及尾置返回类型之后。
虚函数与默认实参
虚函数可以有默认实参,如果某次虚函数调用使用了默认实参,则该实参值由本次调用的静态类型决定:如果通过基类的指针或引用调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类的函数版本。
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。
回避虚函数的机制
有些时候希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本,使用作用域符可以实现这一目的:
double undiscounted = base->Quote::net_price(24);
该代码调用 Quote
的 net_price
函数,不管 base
指向的对象类型到底是什么。
当一个派生类虚函数调用它覆盖的基类的虚函数版本时,一般采用回避机制。此时如果不使用作用域符,则在执行时该调用将被解析成对派生类的调用,从而导致无限递归。
抽象基类
纯虚函数
纯虚函数无需定义,通过在函数体的位置书写 =0
就可以将一个虚函数说明为纯虚函数。其中 =0
只能出现在类内部的虚函数声明语句中。
可以为一个纯虚函数提供定义,不过函数体必须定义在类的外部,也就是说,不能在类的内部为一个 =0
的函数提供函数体。
含有纯虚函数的类是抽象基类
含有或者未经覆盖直接继承纯虚函数的类是抽象基类,抽象基类负责定义接口,而后续的其他类可以覆盖接口。
不能直接创建一个抽象基类的对象。
派生类构造函数只初始化它的直接基类
class Disc_quote :public Quote
{
public:
Disc_quote() = default;
Disc_quote(const std::string& book,double price,std::size_t qty,double disc):
quantity(qty),discount(disc)
protected:
std::size_t quantity = 0;
double discount = 0.0;
};
class Bulk_quote : public Disc_quote
{
public:
Bulk_quote() = default;
Bulk_quote(const std::string& book,double price,std::size_t qty,double disc):
Disc_quote(book,price,qty,disc){}
double net_price(std::size_t)const override;
};
各个类控制其对象的初始化过程,Bulk_quote
没有数据成员,但是仍然需要像原来一样提供一个接受四个参数的构造函数。该构造函数将实参传递给 Disc_quote
的构造函数,随后 Disc_quote
的构造函数将继续调用 Quote
的构造函数。