C++虚函数
虚函数
虚函数的作用
面向对象中一个非常重要的概念叫多态,指的是
使得一个基类指针指向一个派生类对象,当调用基类和派生类共有的函数时,最终调用的是派生类的函数。
如下
class Base{
public:
virtual void f(){
cout<<"Base call f()";
}
};
class Derived : public Base{
public:
virtual void f(){
cout<<"Derived call f()";
}
};
int main(){
Base* pb = new Derived();
pb->f();
}
最终结果如下
虚函数的实现原理
实现该原理的原因是如果类中定义了虚函数,则会在生成对象时生成一个虚函数表指针,这个指针指向虚函数表,虚函数表中存在着类中定义着指向虚函数的指针。
通过这些虚函数,我们可以调用虚函数。
而派生类在继承基类时,也会生成一张虚函数表,虚函数表中存在着父类和自己定义的虚方法。如果重写了基类的虚方法,则会在虚函数表中用派生类重写的方法覆盖其基类原来的虚函数方法。
虚函数实现原理的验证
首先验证
如果类中定义了虚函数,则会在生成对象时生成一个虚函数表指针
class A{
public:
A(){cout<<"call A()"<<endl;}
~A(){cout<<"call A()"<<endl;}
};
class B{
public:
B(){cout<<"call B()"<<endl;}
virtual void virtualBB(){cout<<"call virtualBB()"<<endl;}
virtual void virtualB(){cout<<"call virtualB()"<<endl;}
virtual ~B(){cout<<"call ~B()"<<endl;}
};
int main(){
A a;
B b;
cout<<"sizeof(a) = "<<sizeof(a)<<endl;//it should be one size
cout<<"sizeof(b) = "<<sizeof(b)<<endl;//it should be eight size, a virtual pointer size
}
再验证
派生类在继承基类时,也会生成一张虚函数表,虚函数表中存在着父类和自己定义的虚方法
//author: Solitude
//date: 2021-09-29
//purpose: validate the concept of virtual
#include <iostream>
using namespace std;
class Base{
public:
virtual void f(){
cout<<"Base call f()"<<endl;
}
};
class Derived : public Base{
public:
virtual void f(){
cout<<"Derived call f()"<<endl;
}
};
//validate the concept
typedef void (*PF)();
class A{
public:
A(){cout<<"call A()"<<endl;}
~A(){cout<<"call A()"<<endl;}
};
class B{
public:
B(){cout<<"call B()"<<endl;}
virtual void virtualBB(){cout<<"call virtualBB()"<<endl;}
virtual void virtualB(){cout<<"call virtualB()"<<endl;}
virtual ~B(){cout<<"call ~B()"<<endl;}
};
int main(){
#if 0
Base* pb = new Derived();
pb->f();
#endif
#if 1
A a;
B b;
cout<<"sizeof(a) = "<<sizeof(a)<<endl;//it should be one size
cout<<"sizeof(b) = "<<sizeof(b)<<endl;//it should be eight size, a virtual pointer size
PF pf;
pf = (PF)*((int*)*(int*)(&b)+0);
pf();
pf = (PF)*((int*)*(int*)(&b)+2);
pf();
#endif
return 0;
}
结果如下
typedef void (PF)(); 作用为定义一个函数指针,定义这个指针的目的是用来调用虚函数表中虚函数指针指向的虚函数。
(PF)((int*)(int)(&b)+0);用法有点绕,我解释一下
咱们一步步拆解
(int*)(&b)
(&b)为虚函数指针的地址,我们将之转化为int*类型的指针,这样就获得了一个指向虚函数表的指针。
(int*)(int)(&b)
再将问题变复杂一点,(int)(&b)使得我们得到了虚函数表第一个虚函数指针的位置,我们将之转化为int*类型的指针,这样就获得了一个指向虚函数的指针。
(PF)((int)(int)(&b)+0)
再复杂一点,
((int)(int)(&b)+0)使得我们得到了虚函数表中指向的第一个虚函数的位置,我们再用(PF)将之转化为函数指针的形式。这样我们就得到了函数 virtualBB()的函数指针。
期间加0,加2是为了用得是指针加法,目的是在虚函数表中找到我们的虚函数。
coolshell 陈皓