虚函数定义
基类将类型相关的函数与派生类不做改变直接继承的函数区别对待。对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数。在C++语言中,当我们使用基类的引用或指针调用一个虚函数时会执行动态绑定。因此我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有的虚函数都必须有定义
动态绑定定义
如前所述,当使用基类的引用或指针调用一个虚函数时将发生动态绑定。此时,函数的运行版本由实参决定,即在运行时选择函数的版本,所以动态绑定也被称为运行时绑定。
静态类型和动态类型
当我们使用存在继承关系的类类型时,必须将一个变量或其他表达式的静态类型和动态类型区分。
- 静态类型在编译时总是已知的,是变量声明时的类型或表达式生成的类型
- 动态类型则是变量或表达式表示的内存中的对象的类型,直到运行时才可知
如果表达式既不是引用或指针,则它的动态类型永远与静态类型一致,基类的指针或引用的静态类型可能与其动态类型不一致。
虚函数的调用
通过一个例子来学习:
|
|
注意到,A是一个基类,且其中有虚函数print_classname,而B是A的一个派生类,注意到B中关于print_classname的函数声明前面没有virtual关键字,这是因为基类中的虚函数在派生类中也默认是虚函数;而且B中print_classname参数列表跟着的override关键字,是c++11标准中允许显式地对继承来的虚函数的重新定义。
当虚函数通过指针或引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到引用或指针上的对象的动态类型相匹配的那一个。正如上面代码中,可以看到print函数通过基类的引用作为形参,而在函数体中通过基类的引用调用print_classname。此时,就发生了动态绑定,可以预料到,程序运行的结果就如注释中一般。
注意到main函数中最后调用的print2函数,当我们通过一个普通类型(非引用非指针)的表达式调用虚函数时,在编译时就会将调用的版本确定下来。
派生类中的虚函数
当我们在派生类中覆盖某个虚函数时,可以再使用virtual关键字指出,但不是必须,因为一个函数一旦被声明为虚函数,则在所有的派生类中都是虚函数。
一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型和返回类型必须与被它覆盖的基类函数完全一致。但存在例外,当类的虚函数返回类型是类本身的指针或引用时,派生类中的虚函数的返回类型不需要与基类中相同。
虚函数与默认实参
同其他函数一样,虚函数也可以拥有默认实参。如果某次函数调用使用了默认实参,则该实参值由本次调用的静态类型决定。
即如果我们通过基类的引用或指针调用寒素,则使用基类中定义的默认实参。即使实际运行的是派生类中函数版本也是如此。