在进入虚函数之前先来看看指针,因为虚函数的实现就是通过函数指针来实现的
一时也想不到特别好的指针例子,就看看下面这个
在这个例子中,我们遍历一个结构体的内存,用字符指针来读取其内容。从上图我们可以看到a占4个字节,而且可以看出来是大端或者是小端地址,这里是把‘1’字符对应的整数49放在了0号地址,后面1-3号地址自动补了‘\0’;接下来把b,c存放在4,5号地址中;6,7号地址是空的,并没有补'\0';由于需要内存对齐,d其地址是从8到11,同样其9-11是补了'\0'.
例子主要是想说明一切都是地址的重要性,就像Boy这个结构体一样,除了用Boy *来访问,还可以用char *,int *等指针来访问地址里面的内容。
class Base{
public:
int n;
Base(){this->n=1;};
virtual void f(){printf("1\n");}
virtual int g(){printf("2\n");return 1;}
virtual void h(){printf("3\n");}
};
typedef void (*Fun) (void);
int _tmain(int argc, _TCHAR* argv[])
{
Base b;
int m=2;
Fun pFun=NULL;
printf("虚函数表地址 %p\n",(int *)(&b));
printf("虚函数表地址 %p\n",&(*((int *)(&b))));
printf("n地址 %p\n",&(b.n));
printf("第一个虚函数地址 %p\n",(int *)(*(int *)(&b)));
pFun=(Fun)*((int *)*(int *)(&b));
//pFun();
((Fun)*((int *)*(int *)(&b)+0))();
((Fun)*((int *)*(int *)(&b)+1))();
((Fun)*((int *)*(int *)(&b)+2))();
来看这样一个例子,一个类包含三个虚函数和一个成员变量n,我们通过函数指针的方式来调用这三个虚函数。
先用VS2012来看看虚函数的结构
我们在代码中定义了一个局部变量b,其结构如上图所示,b中包含了一个_vfptr的void **指针,这是一个二级指针,指向虚函数表,虚函数表里面的每一项都是void*的指针;b中还包含了一个成员变量n,其值为1
而且_vfptr在n的前面,这样可以快速地调用虚函数。
然后我们以int *方式读取其内存,结果如上图所示。
我们确实是看到0号地址放_vfptr,1号地址放了成员变量n的值1,2号地址里面就不是b的内容了。
接下来我们来读取虚函数表里面的内容
如图所示,我们还是以int指针的方式来读取其内容,从这个表达式可以看出来一切都是指针的重要性!
其实就是像下面这个图,为了找到相应的函数地址
在多重继承,子类有覆盖父类虚函数及不覆盖虚函数时,子类中的虚函数挂在第一个父类的虚函数表中,如果是覆盖,则覆盖相应的父类的虚函数,否则挂在父类虚函数的后面。
class Base{
public:
int n;
Base(){this->n=1;};
virtual void f(){printf("1\n");}
virtual int g(){printf("2\n");return 1;}
virtual void h(){printf("3\n");}
};
class Base1{
public:
int n;
Base1(){this->n=1;};
virtual void f(){printf("1\n");}
virtual int g(){printf("2\n");return 1;}
virtual void h(){printf("3\n");}
};
class Base2{
public:
int n;
Base2(){this->n=1;};
virtual void f(){printf("1\n");}
virtual int g(){printf("2\n");return 1;}
virtual void h(){printf("3\n");}
};
class Derived:public Base,public Base1,public Base2{
public:
virtual void f(){printf("4\n");}
void g1(){printf("5\n");}
virtual void h1(){printf("6\n");}
};
typedef void (*Fun) (void);
int _tmain(int argc, _TCHAR* argv[])
{
Base b;
Derived d;
我们看多重继承时,虚函数表的情况。
继承类大小为8*3=24个字节,每个父类保存自己的虚函数指针,便于实现多态性
我们展开每个父类,如图
确实是看到有3个_vfptr和3个n。
我们再来看下子类中的虚函数到底是不是挂在第一个父类的虚函数表后面
同样我们还是以int *的方式来读取表里面的函数地址,我们看到在0号地址处,子类中的f方法把父类的f方法覆盖,1,2分别是父类的虚函数,3号是子类的虚函数,4号里面是空的,说明后面已经没有虚函数。这也验证也我们说的子类与父类共用一个虚函数表。