引语:我们在学习c++中经常会碰到一些莫名其妙的性质,特性等,如虚基类继承,虚函数等,但如果从底层,内存,编译器的角度去理解,那将会发现是如此合理且必要。
c++中,我们来看这一个代码
class A
{
pubilc:
void fun() { cout<<现调用为A.fun ;}
int key;
}
class B : publicA //B由公有继承A,继承了A中的fun()函数
{
void fun() { cout<<现调用为B.fun ;} //改写继承来的fun()
}
在main()中
我们可以定义一个 A * p_a //一个A类的指针
再来个 B b //定义了一个B 的对象
并将 p_a=&b ; //将b的地址赋给a 注意,指针的类型是A但指向B类
那么如果我们执行这一条语句 p_a->fun() 那么会调用B类里的fun()还是A类里的呢
事实上 将会输出 现调用为A.fun 即调用的是A类里的fun()函数
为什么要虚函数的存在??
在实际过程中,我们需要即使指针是一个其父类的指针,但当他指向其子类时,仍调用的是子类的函数或数据 例如,当我们使用析构函数的时候,每个继承来的子类都有自己不同的析构函数,因此应当调用的是子类的析构,而不是父类的析构(即使指针类型为父类的指针)
因此 我们引入虚函数的概念
class A
{
pubilc:
virtual void fun() { cout<<现调用为A.fun ;} // 将fun()声明为虚函数
int key;
}
class B : publicA
{
void fun() { cout<<现调用为B.fun ;}
}
这时候在调用 p_a ->fun 输出 现调用为B.fun 满足我们的要求
虚函数是如何实现的?
A a;
B b; //声明两个A B 的对象
每一个含有虚函数的类被定义时,编译将会创建一个对应的虚函数表
而A类的对象中 将会包含一个指向虚函数表的指针
所以 当调用虚函数时,是现通过指向虚函数表的指针找到虚函数表,再在虚函数表中通过偏移量找到fun()函数
而当B继承A的时候,也会生成一个属于B的虚函数表,刚开始的时候,虚函数表中的仍是保留A.fun()地址 ,但当我们重写B中的fun()函数时,将会更新为指向我们重新写的地址
由于我们调用虚函数都需要找到虚函数表,因此,通过更新虚函数表中地址,我们可以实现即使是父类的指针,调用的仍然是子类的虚函数。
更进一步
那虚函数表是保存在哪个区域,栈区,堆区,代码区?
由于虚函数表需要在类定义开始,到程序结束一直存在,因此不能保留在栈区(栈区调用结束即被回收)。。又因为堆区是我们手动申请的区域,而虚函数表是编译器自动分配的,因此不在堆区
所以虚函数表在代码区 和其他的成员函数在同一个区域。完✿✿ヽ(°▽°)ノ✿