<1> 先描述多态的表象
说到虚函数, 首先我们需要来说一下c++中多态的表象: 在基类的成员函数前加上 virtual 关键字修饰,并在派⽣类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数.
如果对象类型是派生类,就调用派生类的函数,如果是基类,就调⽤基类的函数
那么是如何实现运行时确定调用对应的函数?
<2> 虚函数表
以如下代码为例:
class A {public:int i;virtual void func() {}virtual void func2() {}};class B : public A {int j;void func() {}};int main() {printf("sizeof(A)=%d\t sizeof(B)=%d\n", sizeof(A), sizeof(B));}
输出结果如下: :::info sizeof(A)=8 sizeof(B)=12 ::: 结果分析:
类A的对象包括: int i + 类A虚函数表指针地址 4 + 4 = 8 类B的对象包括: int j + int i + 类B虚函数表指针地址 4 + 4 + 4 = 12 若去掉类A中的virtual关键字, 则结果变为 4, 8, 其实这多出来的4字节存放的就是虚函数表地址
当⼀个类中包含虚函数时,编译器会为该类生成⼀个虚函数表,保存该类中虚函数的地址;
当派生类继承该基类,派生类中自然⼀定有虚函数,所以编译器也会为该派生类生成自己的虚函数表。
当我们定义⼀个派生类对象时,编译器检测该类型有虚函数,所以为这个派生类对象生成⼀个虚函数指针,指向该类型的虚函数表,这个虚函数指针的初始化是在构造函数中完成的
1.1 追问:派生类重写基类带默认参数的虚函数,并且在派生类被重写的函数的默认参数值与基类不同,会有什么问题吗?
举例:
class DefaultParaParent {public:virtual void defaultFunc(int x, int y = 0) {printf("call DefaultParaParent defaultFunc\n");}virtual void defaultFunc2(int x, int y = 0) {printf("call DefaultParaParent defaultFunc2\n");}};class DefaultParaChild : public DefaultParaParent {public:virtual void defaultFunc(int c double y = 0.0) {printf("call DefaultParaChild defaultFunc\n");}virtual void defaultFunc2(int c, int v = 4) {printf("call DefaultParaChild defaultFunc2: v=%d\n", v);}};// 以下代码的输出结果是?void TestDefaultReWrite(){DefaultParaParent *d = new DefaultParaChild();d->defaultFunc(22);d->defaultFunc2(33);}
输出如下:
call DefaultParaParent defaultFunc call DefaultParaChild defaultFunc2: v=0
结果分析:
1> 派生类重写基类的 defaultFunc虚函数,但参数列表不一致,所以 defaultFunc方法未重写成功,d->defaultFunc(22)调用的是 DefaultParaParent::defaultFunc;
2> 派生类重写基类的 defaultFunc2虚函数,由于其带有默认参数,所以调用 d->defaultFunc2(33) 时参数 v 的实参值由本次调用的静态类型决定(即该次调用使用的是基类类型的默认参数的值 0)故输出 v = 0
换句话说就是,通过基类的引用或指针调用函数,使用的是基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。
结论:若虚函数使用默认实参,基类和派生类中定义的默认实参最好一致。
1.2 追问:派生类成员函数同时声明为 override 和 final 这样会有什么问题吗?
override 用于标记派生类中重写了基类的虚函数;
final 用于标记该虚函数不允许后续的其他类覆盖;
两者可以同时声明,表明该虚函数是对基类函数的重写,并且不允许被其他函数覆盖。
1.3 不能被声明为虚函数的函数
常见的不不能声明为虚函数的有:普通函数(非成员函数),静态成员函数,内联成员函数,构造函数,友元函数。
1>构造函数
构造函数初始化对象,派⽣类必须知道基类函数干了什么,才能进行构造;当有虚函数时,每⼀个类有⼀个虚表,每⼀个对象有⼀个虚表指针,虚函数表指针在构造函数中初始化
2>内联函数
内联函数表示在编译阶段进行函数体的替换操作, 而虚函数意味着在运行期间进行类型确定, 所以内联函数不能是虚函数
3>静态函数
静态函数不属于对象属于类, 静态成员函数没有this指针, 因此静态函数设置为虚函数没有任何意义
4>友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数 的说法
