面向对象的三个基本特征是:封装、继承、多态。
今天简单说一下C++多态的原理:
首先我们先看一个代码:
class A
{
public:
virtual void foo()
{
cout<<"我是父类"<<endl;
}
}
class B :public A
{
public:
virtual void foo()
{
cout<<"我是子类"<<endl;
}
}
int main(int argc,char**argv)
{
A *p = new B;
p->foo();
return ;
}
类A 中有虚函数,作为虚基类,暂且把A叫做父类,B叫做子类
这个代码main函数中new了一个子类对象B,用父类A类型的指针p指向了子类B对象,p->foo(),这句代码执行的结果不是调用父类中的foo函数,而是调用子类的foo函数
运行结果是: 我是子类
父类中有虚函数,并且子类继承了父类,子类重写了父类的虚函数:
这份代码我们先来看一下编译器做了哪些事情:
对于A类,编译器看到有一个虚函数,在类A中会加上一个指针vfptr(虚函数指针),并且会创建一个表vftable(虚函数表,实质是一个数组,每个元素用来保存函数的地址),
在 这个表中将父类中foo函数的地址放入虚函数表中,刚开始vfptr是没有指向虚函数表的,注意vfptr属于A类对象成员,而vftable不属于A类对象成员;
对于B类,继承了类A,也就继承了类A的vfptr成员,并且编译器也会给B类创建一个虚函数表,这个虚函数表保存了B类中foo函数的地址这个时候vfptr还没有指向vftable;
在main函数中,p->foo()这句代码,编译器会并不会将foo的地址直接绑定上,而是通过vfptr去调用foo函数:
p是一个指针,指向一个对象的地址,我们拿32位编译器来讲,想要得到foo函数的地址,其实很简单, *(int*)p就得到vfptr指针的值,这个值当创建对象之后会执行虚函数表,虚函数表的
第一个成语是foo函数的地址, 我们将foo函数的地址得到,(*(int*)(*(int*)p))得到的是foo函数的地址,调用这个函数(void (*)()) (*(int*)(*(int*)p)) ();
所以当我们的写上p->foo();这句代码的时候,起始编译器是幕后是调用了(void (*)()) (*(int*)(*(int*)p)) ()这个代码
p->foo()等价于(void (*)()) (*(int*)(*(int*)p)) ()
好了,以上就是我们编译器给我们幕后做的工作:
当我们运行程序时,new B,这句代码先是会调用父类A的构造函数,在构造函数中vfptr这个指针会被初始化指向类A中的vftable,然后父类A的构造函数调用完之后,
会调用子类B的构造函数,在B中会将vfptr这个指针指向子类B中的vftable,这样就很清晰了,下面的p->foo(),底层是(void (*)()) (*(int*)(*(int*)p)) ()调用形式,现在的vfptr
指向的是子类B中的虚函数表,自然调用的子类B中的foo函数.
是不是挺简单的,哈哈!!!