class Base
{
public:
virtual void func()
{
...
}
};
Base b1;
1.怎么取到这三个地址?
以这个虚基类为例子:
(1)虚函数表的地址:*(int *)&b1
解释:先取出b1的地址,然后转为int型指针变量,作为一个4个字节的指针变量,之所以之么做,就是为了取到虚基类对象的内存的前4个字节作为整体,就是一个指向虚函数表的指针,所以只要在*解引用,就是虚函数表的地址了
(2)虚函数的地址(以虚函数表的第一个虚函数指针指向的虚函数为例子):
*(int *)*(int *)&b1 ,
解释:*(int *)&b1就是虚函数表的地址,然后取前4个字节作为一个int*指针,这个指针就是指向第一个虚函数的指针,最后用*解引用即可
(3)虚函数指针的地址(以虚函数表中的第二个虚函数指针的地址为例子):
(int *)*(int *)&b1+1
解释:(2)中已经说到,(int *)*(int *)&b1取到的就是指向第一个虚函数的指针,那么,我们直接让这个指针+1,也就是地址移动4个字节,就是第二个虚函数指针的地址了
2.一些重要的原理层次的测试:(具体过程,见注释)
#include<iostream>
#include<string>
using namespace std;
class Base
{
public:
virtual void func()
{
cout << "Base func" << endl; //它的虚函数表应该只有一个 虚函数
}
virtual void func_1()
{
cout << "Base func_1" << endl;
}
};
class D1 :public Base
{
public:
//func不修改它,也就是和Base中的虚函数一摸一样
virtual void func_2()
{
cout << "D1 func_2" << endl;
}
virtual void func_3()
{
cout << "D1 func_3" << endl;
}
//重写
//void func()
//{
//cout << "D1 func" << endl;
//}
};
//测试一个类的 内存大小:
class Test1
{
public:
int m;
void func()
{
cout << "func of Test1" << endl;
}
};
class Test2
{
public:
int m;
virtual void func()
{
cout << "virtual func of Test2" << endl;
}
};
//定义一个函数指针
typedef void(*Func_p)(void);
int main()
{
//测试:
//(1)不同虚函数表 中的同一个虚函数的 地址是否相同
Base b1;
D1 d1;
//正式测试:
//(1)不同vtable中的同一个虚函数的 地址是否相同
cout <<"b1中的 func 虚函数的地址: "<< *(int *)*(int*)&b1 << endl;
Func_p test1 = (Func_p)*(int *)*(int*)&b1;
cout << "d1中的 func 虚函数的地址: " << *(int *)*(int*)&d1 << endl;
Func_p test2 = (Func_p)*(int *)*(int*)&d1;
//测试结果:--1:没有覆盖的话:虚函数的地址是相同的
// --2:如果覆盖了的话:就是两个函数了,所以地址是不相同的
//(2)不同vtable中的同一个虚函数的指针的地址是否相同:
cout << "b1中的 func 虚函数的指针的地址: " << (int *)*(int*)&b1 << endl;
Func_p test3 = (Func_p)*(int *)*(int*)&b1;
cout << "d1中的 func 虚函数的指针的地址: " << (int *)*(int*)&d1 << endl;
Func_p test4 = (Func_p)*(int *)*(int*)&d1;
//(3)同一个vtable中的那些虚函数指针是否相邻(虚函数本身的地址必然不相邻)
cout << "b1中的 func 虚函数 指针的地址: " << (int *)*(int*)&b1 << endl;
Func_p test5 = (Func_p)*(int *)*(int*)&b1;
test5();
cout << "b1中的 func_1 虚函数 指针的地址: " << (int*)*(int*)&b1+1 << endl;
Func_p test6 = (Func_p)*((int *)*(int*)&b1+1);
test6();
//(4)多个类对象的 虚函数表的地址是否相同?
Base b2;
cout << "b1 的虚函数表的 地址: " << *(int *)&b1 << endl;
cout << "b2 的虚函数表的 地址: " << *(int *)&b2 << endl;
//结果相同
//(5)没有虚函数的类 中自然也没有 虚表的指针,自然也不存在虚表
//这一点用 类的内存大小可以测试
cout << "Test1的内存大小:" << sizeof(Test1) << endl;
cout << "Test2的内存大小:" << sizeof(Test2) << endl; //多了一个虚函数表的指针
//(6)其实,从上面可见,子类 和 派生类中 如果没有覆盖就是同一个虚函数,但是不同的虚函数指针
//如果覆盖了,又是2个不同的虚函数
//不过,无论是否重写,都是基类的虚函数的指针排在虚函数表的最前面
//?测试一下这一点
Func_p test7 = (Func_p)*(int *)*(int*)&d1;
test7();
//是
//(7)动态绑定最本质的原理:
//当基类的指针指向派生类的对象时,通过地址移动指向的是 派生类的 虚函数表,所以调用的是派生类的虚函数
//测试--当一个基类的指针指向派生类的对象时,证明 这个指针指向的虚函数表的地址是派生类的虚函数表的
Base* b_p = &d1;
cout << "b_p基类指针指向的对象的 虚函数表的地址是 : " << *(int*)&(*b_p) << endl;
cout << "b1的虚函数表的地址是 : " << *(int *)&b1 << endl;
cout << "d1的虚函数表的地址是: " << *(int *)&d1 << endl;
//(int*)(int)&Base; //先将地址转化为int型,再转化为int*指针类型
//cout << "b1 的地址是 :" << &b1 << endl; //这个取到的是 类对象的地址
//cout << "b1的虚函数表的地址(第一个虚函数指针)取法 :" << (int*)*(int*)&b1 << endl; //这个取到的是虚函数表的地址,???--解释如下
//悟:其实,虚函数表指针,就是一个int*类型的变量,和b1的地址相同,就是它的前4个字节,
//而它指向的位置,才是一个表的开始,所以,先利用&取到b1的地址,然后转为(int*)也就是指针类型的变量
//然后利用*解指针,得到它的指向的东西,它所指向的东西仍然是一个指针(第一个虚函数的指针),所以转化为(int *)类型
//cout << "b1的第二个虚函数指针" << ((int *)*(int*)&b1) + 1 << endl; //int*指针类型+1就是移动4个字节
//利用函数指针调用 虚函数:
//func_p = (int *)*(int *)&b1;
//Func_p func_p1 = (Func_p)*(int *)*(int *)&b1;
//func_p1();
//Func_p func_p2 = (Func_p)*((int *)*(int *)&b1 + 1);//说明(int *)*(int *)&b1就是第一个指针的地址
//func_p2();
//cout << "1:" << *(int *)&b1<<endl; //原来,这个地址是 虚函数指针的地址
//cout << "2: " << *(int *)*(int *)&b1 << endl; //多加了*(int*)之后并不是 原来的指向的东西了,这个地址是 虚函数的地址
//所以,将这个地址 转换为 void (*p)(void) 类型的函数指针,然后就可以调用这个函数了
//cout << "Base 中 func虚函数的地址是 :" << &Base::func << endl;
//cout << "D1 中 func虚函数的 的地址是 : " << &D1::func << endl;
//(2)同一个虚函数表中的 虚函数地址是否相邻?
//cout << "D1中的 func_2的虚函数的地址是: " << &D1::func_2 << endl;
return 0;
}
3.从以上测试代码我们可以得出以下结论:
(1)不同vtable中的同一个虚函数的 地址是否相同?
//测试结果:--1:没有覆盖的话:虚函数的地址是相同的
// --2:如果覆盖了的话:就是两个函数了,所以地址是不相同的
(2)不同vtable中的同一个虚函数的指针的地址是否相同:
不同!
(3)同一个vtable中的那些虚函数指针是否相邻(虚函数本身的地址必然不相邻)
--指向虚函数的指针是相邻的
(4)多个类对象的 虚函数表的地址是否相同?
--相同,说明它们都是共用的虚函数表
(5)没有虚函数的类 中自然也没有 虚表的指针,自然也不存在虚表
(6)无论是否重写,都是基类的虚函数的指针排在虚函数表的最前面
(7)动态绑定最本质的原理:
//当基类的指针指向派生类的对象时,通过地址移动指向的是 派生类的 虚函数表,所以调用的是派生类的虚函数
//测试--当一个基类的指针指向派生类的对象时,证明 这个指针指向的虚函数表的地址是派生类的虚函数表的