自己搞不懂C++虚函数之间的调用关系,特地花费一个下午加一个晚上查资料学习,现在把学到的发上来,供大家学习批评;
在此之前感谢这些大佬的博客等,为我解惑甚多:
https://www.cnblogs.com/hushpa/p/5707475.html
https://blog.csdn.net/blight_888/article/details/69055766
https://www.cnblogs.com/vipchenwei/p/7466018.html?utm_source=debugrun&utm_medium=referral
https://www.cnblogs.com/malecrab/p/5572730.html
直接上货,测试代码https://download.csdn.net/download/cold_windx/10717198
1、虚表与虚表指针
C++中的虚函数的实现一般是通过虚函数表(V-Table)来实现的。虚函数表实际上是一块连续的内存,每个内存单元中记录一个虚函数地址。每个类的虚函数表被该类的所有对象所共享。32位系统下,如果一个类存在N个虚函数,该类的虚表占N*4个字节。只有虚类才有虚函数表。
虚类的对象,除自身的成员变量外还存在一个虚表指针(vfptr),该指针指向该类的虚表的起始地址。因此,虚类对象的内存大小 = 成员变量大小(考虑对齐) + vfptr大小。
虚标指针在内存空间中位于成员变量之前(有的说位于之后,至少我用vs测试是在之前)。
见测试代码main1.cpp
2、单继承下的虚表
以上考虑的是基类的虚表,现在来看看派生子类的虚表构造过程:
见测试代码main2.cpp
3、多继承下的虚表
多继承时,派生类的虚表构造与单继承基本无异,只是此时派生子类自己的虚函数要加在第一个继承的基类的虚表末尾。
见测试代码main3.cpp
4、类型兼容性原则
类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。类型兼容规则是多态性的重要基础之一。
类型兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
当父类指针(引用)指向子类对象时,子类对象退化成父类对象,只能访问父类中定义的成员。
但是函数又有virtual修饰的话,就会展现多态行为,会根据实际指针指向的对象判断函数的调用。
5、虚函数表解释多态
类型转换原则很难记忆,但使用虚函数表来解释便很容易理解:
public:
virtual void func1() { cout << "Base1::func1\n"; }
virtual void func2() { cout << "Base1::func2\n"; }
int b1;
}base1;
class Base2
{
public:
virtual void func1() { cout << "Base2::func1\n"; }
virtual void func2() { cout << "Base2::func2\n"; }
int b2;
}base2;
class Derive : public Base1, public Base2
{
public:
virtual void func1() { cout << "Derive::func1\n"; }
virtual void func3() { cout << "Derive::func3\n"; }
int d;
}derive;
1》派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
无论是Base1 b(derive)还是Base1 b = new Base1(derive),对象b是作为Base1基类的对象,因此对象b只能调用Base1中已经声明的成员;
2》派生类对象赋值基类的对象时,具体调用的函数是基类的函数;而基类指针或引用派生类对象时具体调用的函数要看派生类有没有覆盖这个函数。
若派生类对象赋值基类的对象,即Base1 b(derive),此时只不过是使用derive的成员给b的成员赋值,而derive的虚表并没用赋值给b,对象b依然沿用基类的虚表,实际调用的函数当然是基类Base1的函数;
若基类指针或引用派生类对象,即Base1 *pb1 = new Derive()获Base2& pb2 = derive,此时b并没有建立新的对象,而是沿用derive的内存空间。看下图:
此时,若pb2调用Base2中声明的函数时,会通过虚表指针vfptr2查找对应函数,因为vfptr2是原派生类对象derive的虚表指针,所以调用的是原本类型(即派生类)的函数成员。
见测试代码main4.cpp