总所周知,虚函数是实现多态的基础。
大家在网上搜索关于虚函数的博客应该都会搜到陈皓写的那篇C++ 虚函数表解析吧,这篇文章确实不错,画的图也比较好理解,对于指针理解比较深刻的人应该不会理解错误,但对于新人来说可能还是有点不友好。以下几点我觉得需要强调:
还有一点就是在该大神的例子程序的输出中,给出的中文解释我认为是错误的,看起来是很容易误导人的。 最开始的例子程序中的:
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
这两句明显错误,本人在困惑之余便开始了自己的验证。
而且图例中也应该有两次指针指向的过程。
虚函数表的指针存储在对象实例中最前面的位置。
这意味着我们可以通过对象实例的地址得到这个虚函数表的指针,然后就遍历虚函数表中的各个函数指针,然后调用相应的函数。
下面开始各个例子程序的实验!(win10+vs2017)
#include "pch.h"
#include
using namespace std;
class Base {
public:
virtual void f() {
cout << "Base::f" << endl; }
virtual void g() {
cout << "Base::g" << endl; }
virtual void h() {
cout << "Base::h" << endl; }
};
int main()
{
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
Base * p = &b;
cout << "该对象的地址:" << p << endl;
cout << "虚函数表的指针也是从这个地址"<< (int*)(&b) <<"开始存的" << endl << endl;
cout << "虚函数表的指针指向的地址10进制:" << *(int*)(&b) << "即虚函数表的指针存的内容"<<endl;
cout << "即虚函数表的地址:" << (int*)*(int*)(&b) << endl << endl;
pFun = (Fun)*(int*)*(int*)(&b);//第一个虚函数的指针
cout << "第一个虚函数的地址:" << pFun << endl;
pFun();
Fun gFun = NULL;
gFun = (Fun)*((int*)*(int*)(&b) + 1);//第二个虚函数的指针
Fun hFun = NULL;
hFun = (Fun)*((int*)*(int*)(&b) + 2);//第三个虚函数的指针
}
b
返回Base
类型的对象。&b
返回Base *
类型的指针。(int *)(&b)
将Base *
类型的指针转换为int *
类型的指针,转换后指针指向地址没变,但指向对象的类型变了。*(int *)(&b)
对int *
类型的指针解引用,从地址开始的那个字节开始,取出sizeof(int)个字节,赋值给一个int对象(因为指针认为自己指向一个int对象)。(int *)*(int *)(&b)
相当于 (int *)
后接一个int值,返回一个int指针,将这个int值作为该指针指向的地址值。*(int *)*(int *)(&b)
对int *
类型的指针解引用,返回int值。(Fun)*(int *)*(int *)(&b)
,Fun是函数指针,后接一个int值,将这个int值作为该函数指针指向的地址值。gFun = (Fun)*((int*)*(int*)(&b) + 1);
了。首先(int*)*(int*)(&b)
将虚函数表的指针转换为指向指针数组首元素的指针(即转换过程中,指针指向地址没变的),然后((int*)*(int*)(&b) + 1)
这里就是数组的指针的正常操作,现在这个指针指向了数组的第二个元素(即第二个虚函数指针),最后就是解引用,然后转换为Fun
函数指针。如果你还没有理解某个步骤,建议直接查看以下图例的大图,配合debug显示的局部变量表使用,再回头看整个过程。
上图解释了虚函数的实现机制:
在上面例子中还需要讲一个细节,在虚函数表最后位置有一个字节用来标志虚函数表的结束。
char* end = NULL;
end = (char*)((int*)*(int*)(&b) + 3);
加入如上代码便可以得到结束标志,((int*)*(int*)(&b) + 3)
这里指向了虚函数表即指针数组的第四个元素,但实际上数组里只有三个指针,所以这里便刚好指向了结束标志。再通过(char*)
转换指针类型,代表指向的是一个字节。
由于我是第二次运行程序,所以地址有点不一样。这里end指针存的地址,按照之前的例子应该是0x00305b38再加12。
这里你最好再明确下char型存储的含义:(即ASCII表中:是int型<---->char型的相互转换关系)
char end1 = '\0';//字符串的结束符
char end2 = 0;//字符串的结束符
char zero1 = '0';//这才是真正的字符0
char zero2 = 48;
在此例中,基类有三个虚函数,派生类也有三个虚函数,但派生类一个虚函数也没有去重写。
#include "pch.h"
#include
using namespace std;
class Base {
public:
virtual void f() {
cout << "Base::f" << endl; }
virtual void g() {
cout << "Base::g" << endl; }
virtual void h() {
cout << "Base::h" << endl; }
};
class Derive : public Base {
public:
virtual void f1() {
cout << "Derive::f" << endl; }
virtual void g1() {
cout << "Derive::g" << endl; }
virtual void h1() {
cout << "Derive::h" << endl; }
};
int main()
{
typedef void(*Fun)(void);
Derive d;
Base *p = &d;
Fun fFun = NULL;
fFun = (Fun)*((int*)*(int*)(&d) + 0);//第一个虚函数的指针
Fun gFun = NULL;
gFun = (Fun)*((int*)*(int*)(&d) + 1);//第二个虚函数的指针
Fun hFun = NULL;
hFun = (Fun)*((int*)*(int*)(&d) + 2);//第三个虚函数的指针
Fun f1 = NULL;
f1 = (Fun)*((int*)*(int*)(&d) + 3);
Fun g1 = NULL;
g1 = (Fun)*((int*)*(int*)(&d) + 4);
Fun h1 = NULL;
h1 = (Fun)*((int*)*(int*)(&d) + 5);
char* end = NULL;
end = (char*)((int*)*(int*)(&d) + 6);
}
虽然虚函数表里只能显示父类的虚函数,但通过增加数组指针的方法,一样可以获得派生类的虚函数指针。就算这里是Derive *p1 = &d;
也一样,只显示基类的三个虚函数。
虚函数表的内存模型如下:
但这里我已经厌倦了给每个虚函数生成一个函数指针,所以可以用以下循环:
int main()
{
typedef void(*Fun)(void);
Derive d;
int *vTable = (int *)*(int *)(&d);//虚函数表的指针
for (int i = 0; i<6; ++i)//判断条件写成vTable[i] != 0,有可能会报异常
{
printf("function : %d :0X%x->", i, vTable[i]);
Fun f = (Fun)(vTable[i]);
f(); //访问虚函数
}
}
vTable[i]
相当于给vTable指针加i
,再解引用。其实就是数组的用法啦,所以就少了解引用的一步。
打印出来的是各个虚函数的地址。
#include "pch.h"
#include
using namespace std;
class Base {
public:
virtual void f() {
cout << "Base::f" << endl; }
virtual void g() {
cout << "Base::g" << endl; }
virtual void h() {
cout << "Base::h" << endl; }
};
class Derive : public Base {
public:
virtual void f() {
cout << "Derive::f" << endl; }
virtual void g1() {
cout << "Derive::g" << endl; }
virtual void h1() {
cout << "Derive::h" << endl; }
};
int main()
{
typedef void(*Fun)(void);
Derive d;
int *vTable = (int *)*(int *)(&d);
for (int i = 0; i<5; ++i)
{
printf("function : %d :0X%x->", i, vTable[i]);
Fun f = (Fun)(vTable[i]);
f(); //访问虚函数
}
}
在此例中,有三个基类,一个派生类,且派生类一个虚函数也没有去重写。
#include "pch.h"
#include
using namespace std;
class Base1 {
public:
virtual void f() {
cout << "Base1::f" << endl; }
virtual void g() {
cout << "Base1::g" << endl; }
virtual void h() {
cout << "Base1::h" << endl; }
};
class Base2 {
public:
virtual void f() {
cout << "Base2::f" << endl; }
virtual void g() {
cout << "Base2::g" << endl; }
virtual void h() {
cout << "Base2::h" << endl; }
};
class Base3 {
public:
virtual void f() {
cout << "Base3::f" << endl; }
virtual void g() {
cout << "Base3::g" << endl; }
virtual void h() {
cout << "Base3::h" << endl; }
};
class Derive : public Base1,public Base2, public Base3 {
public:
virtual void f1() {
cout << "Derive::f" << endl; }
virtual void g1() {
cout << "Derive::g" << endl; }
virtual void h1() {
cout << "Derive::h" << endl; }
};
typedef void(*Fun)(void);
void printVfun(int n,int * vTable) {
for (int i = 0; i < n; ++i)
{
printf("function : %d :0X%x->", i, vTable[i]);
Fun f = (Fun)(vTable[i]);
f(); //访问虚函数
}
cout << "" << endl;
}
int main()
{
Derive d;
int *vTable1 = (int *)*(int *)(&d);//第一个虚函数表的指针
printVfun(6, vTable1);
int *vTable2 = (int *)*((int *)(&d)+1);//第二个虚函数表的指针
printVfun(3, vTable2);
int *vTable3 = (int *)*((int *)(&d) + 2);//第三个虚函数表的指针
printVfun(3, vTable3);
}
在此例中,有三个基类,一个派生类,且派生类重写了三个基类的同一个虚函数。
#include "pch.h"
#include
using namespace std;
class Base1 {
public:
virtual void f() {
cout << "Base1::f" << endl; }
virtual void g() {
cout << "Base1::g" << endl; }
virtual void h() {
cout << "Base1::h" << endl; }
};
class Base2 {
public:
virtual void f() {
cout << "Base2::f" << endl; }
virtual void g() {
cout << "Base2::g" << endl; }
virtual void h() {
cout << "Base2::h" << endl; }
};
class Base3 {
public:
virtual void f() {
cout << "Base3::f" << endl; }
virtual void g() {
cout << "Base3::g" << endl; }
virtual void h() {
cout << "Base3::h" << endl; }
};
class Derive : public Base1, public Base2, public Base3 {
public:
virtual void f() {
cout << "Derive::f" << endl; }
virtual void g1() {
cout << "Derive::g" << endl; }
virtual void h1() {
cout << "Derive::h" << endl; }
};
typedef void(*Fun)(void);
void printVfun(int n, int * vTable) {
for (int i = 0; i < n; ++i)
{
printf("function : %d :0X%x->", i, vTable[i]);
Fun f = (Fun)(vTable[i]);
f(); //访问虚函数
}
cout << "" << endl;
}
int main()
{
Derive d;
int *vTable1 = (int *)*(int *)(&d);//第一个虚函数表的指针
printVfun(5, vTable1);
int *vTable2 = (int *)*((int *)(&d) + 1);//第二个虚函数表的指针
printVfun(3, vTable2);
int *vTable3 = (int *)*((int *)(&d) + 2);//第三个虚函数表的指针
printVfun(3, vTable3);
}
注意本章中的示意图都只会关注基类的虚函数指针。或者因为重写,而导致在虚函数表中基类的虚函数指针被替换的情况。(就像局部变量图中的一样)
在该例中运行:
Base b1;
Base b2;
Derive d1;
Derive d2;
Base b;
Derive d;
Base1 b1;
Base2 b2;
Base3 b3;
Derive d;