赞
踩
总所周知,虚函数是实现多态的基础。
大家在网上搜索关于虚函数的博客应该都会搜到陈皓写的那篇C++ 虚函数表解析吧,这篇文章确实不错,画的图也比较好理解,对于指针理解比较深刻的人应该不会理解错误,但对于新人来说可能还是有点不友好。以下几点我觉得需要强调:
还有一点就是在该大神的例子程序的输出中,给出的中文解释我认为是错误的,看起来是很容易误导人的。 最开始的例子程序中的:
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;
这两句明显错误,本人在困惑之余便开始了自己的验证。
而且图例中也应该有两次指针指向的过程。
虚函数表的指针存储在对象实例中最前面的位置。
这意味着我们可以通过对象实例的地址得到这个虚函数表的指针,然后就遍历虚函数表中的各个函数指针,然后调用相应的函数。
下面开始各个例子程序的实验!(win10+vs2017)
#include "pch.h" #include <iostream> 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 <iostream> 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
,再解引用。其实就是数组的用法啦,所以就少了解引用的一步。
打印出来的是各个虚函数的地址。
在此例中,派生类只覆盖了基类的一个函数:f()。
#include "pch.h" #include <iostream> 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 <iostream> 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 <iostream> 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;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。