接上文,本文从内存角度理解虚函数。
(1)首先看一下virtual加和不加对内存大小的影响:
不加virtual
class CPerson
{
...
void speak() {
//printf("说人话rn");
...
};
三个类的大小:
加virtual
class CPerson
{
...
virtual void speak() {
//printf("说人话rn");
...
};
三个类的大小:
(2)为什么加了virtual会比不加virtual大了四个字节呢?
先看看加了virtual之后多了什么东西,从下图可以看出chs对象中多出了一个地址,并且地址里面还包含一个地址,看起来像是指向函数的,是不是这样呢?
为了查看指定内存的内容,只需要像下图将相应的地址拖入左侧的内从中即可,既可以改变内存地址,又可以改变内容
为了探究这个问题,我们需要从汇编的角度去看,但怎么去查看呢?查看的方法如下
设置好断点之后,在汇编界面即可看到相应地址内容,第一个地址没对上,还没弄懂,但是函数的地址是与我们在“监视“中看到的是一致的。
F11之后就会跳入指向的函数
再找一个对象看看,发现两个指向函数的地址都是一样的,这个时候是不是感觉到虚函数作用的原理了?
简单一些总结就是:间接调用
3.虚函数原理总结
//虚函数的原理
//1.虚函数的调用方法是间接调用(先查虚表地址,再查虚表中的虚函数指针)
//2.增加了虚函数virtual关键字的对象头部4个字节是虚表地址(某些情况:单继承)
原理图如下:
4.模拟上述调用过程的
5.学习视频地址:深入理解虚函数的原理
6.学习笔记:
#include//面向对象:多态 //虚函数的原理 //1.虚函数的调用方法是间接调用(先查虚表地址,再查虚表中的虚函数指针) //2.增加了虚函数virtual关键字的对象头部4个字节是虚表地址(某些情况:单继承) class CPerson { public: CPerson() { m_nType = 0; } //虚函数 //多态:某一个函数在父类子类有不同的实现,运行时对象自行决定调用哪一个类下实现 virtual void speak() { //printf("说人话rn"); } int m_nType; }; class CChinese :public CPerson { public: CChinese() { m_nType = 1; } void speak() { printf("说中文rn"); } }; class CEnglish :public CPerson { public: CEnglish() { m_nType = 2; } void speak() { printf("说英语rn"); } }; //定义一个类的成员函数指针的类型 typedef void(CPerson::*PFN_SPEAK)(); union MyFuncAddr { unsigned int n; PFN_SPEAK pfn; }; int main(int argc,char* argv[]) { CChinese chs; CChinese chs2; CEnglish eng; //eng.speak(); chs.speak(); int nPersonsize = sizeof(CPerson); int nChinesesize = sizeof(CChinese); int nEnglishsize = sizeof(CEnglish); //一群人,每个人说自己的语言 //CPerson ary[3];//只能代表有三个人 CPerson* ary[3]; //用父类指针指向子类对象 //把子类指针强转为父类,父类的三个指针调用的都是父类的 ary[0] = &chs; ary[1] = ŋ ary[2] = &chs2; //模拟每个人说话 for (int i = 0; i < 3; i++) { printf("%d:",i+1); ary[i]->speak();//间接调用 //猜测编译器的工作过程 //void* pObjtAddr = ary[0];//获取对象地址,代表对象的地址0x00bcfba0 //unsigned int pAddr = *(unsigned int*)pObjtAddr;//获取对象首四字节的地址 //MyFuncAddr nFunAddr; //nFunAddr.n = *(unsigned int*)pAddr;//获取对象首四字节所指向的函数指针的地址 //(ary[i]->*nFunAddr.pfn)(); //int n = 1; //virtual达到的效果 //if (ary[i]->m_nType == 1) //{ // //表示该对象是中国人 // //父类转子类后再子类转父类是可以强制转换的 // CChinese* pChs = (CChinese*)ary[i]; // pChs->speak(); //} //else if (ary[i]->m_nType == 2) //{ // //表示该对象是英国人 // //父类转子类后再子类转父类是可以强制转换的 // CEnglish* pEng = (CEnglish*)ary[i]; // pEng->speak(); //} //ary[i]->speak(); } return 0; }



