如果一个类中,有虚函数,针对这个类会产生一个虚函数表。
生成这个类对象的时候,会有一个虚函数表指针,这个指针会指向这个虚函数表的开始地址。
我们本节就研究这个vptr指针。注意,vptr指针在 类对象中的位置。
证明 虚函数表指针 存在类对象中
class Teacher37 {
public:
virtual void Teacher37func1() {
cout << "Teacher37func1 start " << endl;
}
virtual void Teacher37func2() {
cout << "Teacher37func2 start " << endl;
}
virtual int Teacher37func3() {
cout << "Teacher37func3 start " << endl;
return 888;
}
public:
int mage;
};
void main() {
cout << sizeof(int) << endl;//vs2017 32bit下大小为 4 64位下大小为8
cout << sizeof(int *) << endl;//vs 2017 32bit下大小为 4 64位下大小为4
cout<<sizeof(Teacher37)<<endl;//vs 2917 32bit下大小为 8 64位下大小为16,因为有内存对齐,因此是16
//我们当前以32bit下研究,sizeof(Teacher37)大小为8,说明除了int mage占用4个字节外,还有4个字节被占用
//这4个字节就是vptr指针的大小
}
证明 虚函数表指针 的位置,以及如何找到这个指针的位置并调用
class Teacher37 {
public:
virtual void Teacher37func1() {
cout << "Teacher37func1 start " << endl;
}
virtual void Teacher37func2() {
cout << "Teacher37func2 start " << endl;
}
virtual int Teacher37func3() {
cout << "Teacher37func3 start " << endl;
return 888;
}
public:
int mage;
};
//证明vptr指针的位置
void main() {
Teacher37 * ptea = new Teacher37();
long * templong = reinterpret_cast<long *>(ptea);
cout << "ptea address = " << ptea << " templong = " << templong<< endl;
int *pmage = &(ptea->mage);
cout << "pmage = " << pmage << endl;
int *pmagePrevious = pmage - 1;
cout << "pmagePrevious = " << pmagePrevious << endl;
delete ptea;
// ptea address = 01060880 templong = 01060880
// pmage = 01060884
// pmagePrevious = 01060880
//从上述结果可以看到,Teacher37对象的大小是8,mage占用了4个,而且是后4个字节,那么vptr占用的是前4个字节。
//如何找到这个vptr指向的函数,并且调用.
//思路是使用vptr指针的++ 或者vptr指针[], 找到vptr指针指向的函数,那么首先就要找到 指向func的指针,
//又因为 指针的++,步长为指针指向的数据的 长度,因此我们要找到 vptr指向的
Teacher37 * ptea1 = new Teacher37();
//将其 强制转换成 long*
long * tempptea1 = reinterpret_cast<long *>(ptea1);
//注意这时候还要转,先写出来,后面再解释
//tempptea1 的指向就是当前Teacher37,由于vptr是Teacher37的第一个“元素”,因此*tempptea1指向的是teacher37,也指向的是vptr,从前面的知识我们知道vptr指向的数据就是 虚函数表
long *vptr= (long *)(*tempptea1);
cout << sizeof(long) << endl;//在32位下 long也占用4个字节,int要占用4个字节,指针也占用4个字节,因此可以使用long * 接受。
typedef void(*FUNTYPE)(void);//定义函数指针类型 返回值是void,参数也是void
//用函数指针类型 定义一个变量,注意这个变量是一个指针,
//通过 templong1[x] 指向不同的指针,这块要看懂,需要懂函数指针的意义,如果不懂,或者不清晰,可以参考https://mp.csdn.net/mp_blog/creation/editor/135103006
FUNTYPE funpoint1 = (FUNTYPE)(vptr[0]);
FUNTYPE funpoint2 = (FUNTYPE)(vptr[1]);
FUNTYPE funpoint3 = (FUNTYPE)(vptr[2]);
//那么这时候 理论 funpoint1 就指向Teacher37func1()方法了
//调用:
cout << "---------------" << endl;
funpoint1();
funpoint2();
funpoint3();
cout << "---------------" << endl;
delete ptea1;
//ptea address = 00A308C8 templong = 00A308C8
// pmage = 00A308CC
// pmagePrevious = 00A308C8
// 4
// -------------- -
// Teacher37func1 start
// Teacher37func2 start
// Teacher37func3 start
// -------------- -
}
上述代码关键点解析:从基础开始捋一遍,理解不了的地方,画内存模型图,就能看明白了
class Teacher38 {
public:
int *p;
long long templong;
};
void main() {
//相关知识点整理:
//1. 指针的[],
int *pint = new int[3];//定义一个指针,指向一个3个int的空间
for (size_t i = 0; i < 3; i++)
{
pint[i] = i * 6;
}
for (size_t i = 0; i < 3; i++)
{
cout << pint[i] << endl;
}
cout << "----------------------" << endl;
cout<<pint<<endl;
for (size_t i = 0; i < 3; i++)
{
cout << &(pint[i]) << endl;
}
// 0
// 6
// 12
// ----------------------
// 0121EA98
// 0121EA98 指针的[0] 和指针的[1]之间的差距是 指针指向的数据,由于指针是int *pint,指针指向的数据是int,因此[0]和[1]相差一个int的大小,在32位 vs2017上是4个字节
// 0121EA9C
// 0121EAA0
//2 指针的++,即指针的步长
cout <<"sizeof(long long) = " << sizeof(long long) << endl;//在 win32 上 是8
long long * plong = new long long[5];
long long * tempplong = plong;
for (size_t i = 0; i < 5; i++)
{
//打印 tempplong 指向的空间的地址是啥
cout << tempplong << endl;
*tempplong = i * 8;//给tempplong指向的空间的内容赋值
tempplong++;// ++后,再次循环查看plong 的地址
}
//从结果来看,tempplong++的每次加的是8,也就是long long 的大小
//sizeof(long long) = 8
// 00D26E00
// 00D26E08
// 00D26E10
// 00D26E18
// 00D26E20
for (int i = 0; i < 5;i++) {
cout << *plong << endl;
plong++;
}
// 0
// 8
// 16
// 24
// 32
//3.类中 变量 使用的 技巧,我们这里以Teacher38 为例分析
Teacher38 tea;
tea.p = new int[3];
for (size_t i = 0; i < 3; i++)
{
//后面还要使用tea.p,因此不要使用++操作,
tea.p[i] = i * 8;
}
tea.templong = 999;
cout << "正常使用 start " << endl;
for (size_t i = 0; i < 3; i++)
{
cout << "tea.p["<< i << "]" << tea.p[i] << endl;
}
cout << "tea.templong = " << tea.templong << endl;
cout << "正常使用 end " << endl;
cout << "这里要明白指针本身,和指针指向内容的两个概念,复习一下" << endl;
cout << "打印&tea的地址和&tea.p的地址 这两个是一样的 &tea = " << &tea << " &tea.p = " << &tea.p << endl;
cout << "&tea.p 和 tea.p 这两个是不一样的 &tea.p代表的是指针的地址,tea.p代表的是指针指向的内容,虽然这个内容也是地址,&tea.p = " << &tea.p << " tea.p = " << tea.p << endl;
//4. 下面我们就要模仿上述代码 ,在不知道vptr地址的情况下,只是知道&tea的地址,怎么访问vptr指向的数据
Teacher38 *ptea = &tea;
cout << "ptea 指向是tea的地址 ,因此 ptea 和 &tea是一样的" << ptea << " & tea = " << &tea << endl;
//那么怎么通过已经知道的ptea (也就是&tea的值)算出来 ptea->teapptea的值呢?
//4.1 先将 ptea 强制类型转换成 long *,为什么是long* 呢?这是因为 指针类型的大小是 long,也就是常说的32 位下是4,64位下是8,实际上是long
long * tempptea = (long *)ptea;
cout << "tempptea = " << tempptea << endl;
//那么这时候 tempptea 和ptea是指向同一个地址了,注意这里:但是会被强转成long *
//4.2 然后 再 使用 *tempptea, 将tempptea中的值取出来,我们知道在C语言中 *在取值的时候,代表将teaptea指向的数据取出来
//那么*tempptea 就代表 temmptea 指向内容的,temptea指向的内容是tea tea的内部布局是这样的,第一个变量是int *p,因此可以用 long *p 接,
long* temmptea2 = (long *)(*tempptea);
cout << "temmptea2 = " << temmptea2 << endl;//实际上就是 int *p中存储的值
for (size_t i = 0; i < 3; i++)
{
cout << "temmptea2 = " << &(temmptea2[i]) << endl;
cout << "temmptea2[" << i << "] = " << temmptea2[i] << endl;
}
//正常使用 start
// tea.p[0]0
// tea.p[1]8
// tea.p[2]16
// tea.templong = 999
// 正常使用 end
// 这里要明白指针本身,和指针指向内容的两个概念,复习一下
// 打印&tea的地址和&tea.p的地址 这两个是一样的 &tea = 00C1FA0C &tea.p = 00C1FA0C
// &tea.p 和 tea.p 这两个是不一样的 &tea.p代表的是指针的地址,tea.p代表的是指针指向的内容,虽然这个内容也是地址,&tea.p = 00C1FA0C tea.p = 010A0D98
// ptea 指向是tea的地址, 因此 ptea 和 &tea是一样的00C1FA0C & tea = 00C1FA0C
// tempptea = 00C1FA0C
// temmptea2 = 010A0D98
// temmptea2 = 010A0D98
// temmptea2[0] = 0
// temmptea2 = 010A0D9C
// temmptea2[1] = 8
// temmptea2 = 010A0DA0
// temmptea2[2] = 16
}
基类和子类中有虚函数表
假设第二个虚函数,子类有重写 基类的第二个虚函数,图标如下