【C++】多态(二)
多态的实现原理
- 多态实现原理
- 对象模型(带有虚函数的类对象的模型)
- 多态的原理
- 多态的分类
- 多继承体系中虚函数存储
不同的编译器对于多态底层实现原理细节上可能会有差异(当前使用 vs2017 32位 编译)
多态实现原理
对象模型(带有虚函数的类对象的模型)
1、基类虚函数存储
首先来看看,在一个类中 虚函数是如何进行存储的:
(1)一个类有一个对象:
(2)一个类有多个对象:
由此可总结:
当一个类中包含有虚函数的时候,该类在定义对象时候编译器会自动调用类的构造方法来实现虚函数表指针信息的初始化,虚表指针指向一个地址,该地址中存储的是类中所有的虚函数入口地址信息(按照虚函数的声明顺序存储在虚表指针所指向的地址中),并且对于同一个类所定义的对象,它们的虚表指针是相同的
2、子类虚函数存储
(1)当继承关系中没有虚函数:
class A {
public:
void Func1()
{
cout << "A::Func1()" << endl;
}
public:
int _a;
};
class B :public A {
public:
void Func()
{
cout << "B::Func()" << endl;
}
public:
int _b;
};
(2)当继承关系中存在虚函数
不存在虚函数的重写时:
class A {
public:
virtual void Func1()
{
cout << "A::Func1()" << endl;
}
public:
int _a;
};
class B :public A {
public:
void Func()
{
cout << "B::Func()" << endl;
}
public:
int _b;
};
当存在虚函数的重写时:
class A {
public:
virtual void Func1()
{
cout << "A::Func1()" << endl;
}
public:
int _a;
};
class B :public A {
public:
virtual void Func1() //构成虚函数重写
{
cout << "B::Func1()" << endl;
}
public:
int _b;
};
class A {
public:
virtual void Func1()
{
cout << "A::Func1()" << endl;
}
virtual void Func2()
{
cout << "A::Func2()" << endl;
}
virtual void Func3()
{
cout << "A::Func3()" << endl;
}
public:
int _a;
};
class B :public A {
public:
virtual void Func1() //构成重写
{
cout << "B::Func1()" << endl;
}
virtual void Func3() //构成重写
{
cout << "B::Func3()" << endl;
}
public:
int _b;
};
当子类中存在新添加的虚函数信息时,虚表指针指向的空间中又是如何存储的呢?
虚函数表指针所指向的空间中存放的是虚函数的入口地址信息,派生类的虚表生成规则:
(1)先将基类中虚表内容拷贝一份到派生类虚表中;
(2)若派生类重写了基类中某个虚函数,则用派生类自己的虚函数覆盖虚表中被重写的基类的虚函数;
(3)派生类自己新增加的虚函数按照其在派生类中声明次序增加在派生类虚表的最后。
class A {
public:
virtual void Func1()
{
cout << "A::Func1()" << endl;
}
virtual void Func2()
{
cout << "A::Func2()" << endl;
}
virtual void Func3()
{
cout << "A::Func3()" << endl;
}
public:
int _a;
};
class B :public A {
public:
virtual void Func1() //构成重写
{
cout << "B::Func1()" << endl;
}
virtual void Func3() //构成重写
{
cout << "B::Func3()" << endl;
}
virtual void Func4()
{
cout << "B::Func4()" << endl;
}
virtual void Func5()
{
cout << "B::Func5()" << endl;
}
public:
int _b;
};
多态的原理
class A {
public:
virtual void Func1() //虚函数
{
cout << "A::Func1()" << endl;
}
virtual void Func2() //虚函数
{
cout << "A::Func2()" << endl;
}
void Func3() //普通函数
{
cout << "A::Func3()" << endl;
}
public:
int _a;
};
class B :public A {
public:
virtual void Func1() //重写
{
cout << "B::Func1()" << endl;
}
public :
int _b;
};
void Test(A pa)
{ //参数--->传值方式
pa.Func1();
pa.Func2();
pa.Func3();
}
void Test(A* pa) //形成函数重载
{ //基类指针方式接收参数
pa->Func1();
pa->Func2();
pa->Func3();
}
分析:
构成多态的条件:
(1)基类中存在虚函数,子类中重写了基类虚函数;
(2)基类的指针 / 引用 ,可以 指向 / 引用子类的对象
多态的分类
(1)静态多态-------函数重载
(2)动态多态-------运行时多态,程序运行期间根据具体拿到的类型确定程序的具体行为,调用具体的函数
多继承体系中虚函数存储
多继承中,子类新增加的虚函数地址存储在第一个继承体系的虚表最后
//定义两个不同的类
class B1 {
public:
int _b1;
virtual void Func1()
{
cout << "B1::Func1()" << endl;
}
virtual void Func2()
{
cout << "B1::Func2()" << endl;
}
};
class B2 {
public:
int _b2;
virtual void Func3()
{
cout << "B2::Func3()" << endl;
}
virtual void Func4()
{
cout << "B2::Func4()" << endl;
}
};
//实现多继承
class D :public B1, public B2
{
public:
int _d;
virtual void Func1() //与 B1 类中虚函数实现重写
{
cout << "D::Func1()" << endl;
}
virtual void Func4() //与 B2 类中虚函数实现重写
{
cout << "D::Func4()" << endl;
}
virtual void Func5() //子类自己的虚函数
{
cout << "D::Func5()" << endl;
}
};
前边我们介绍到, 子类的虚表建立过程为: (1)将基类中虚表信息拷贝过来;
(2)检查子类中是否存在基类虚函数的重写,若有则需要修改子类虚表中重写的虚函数入口地址; (3)子类自己的虚函数放在虚表最后
因此,我们猜想在多继承体系中也是分为三个步骤:直接将基类虚表拷贝过来,然后检测虚函数重写,添加子类自己的虚函数,接下来我们来验证一下~~
定义两个函数,分别用来获取子类 D 中由继承 B1 , B2 类而来的两个虚表中的信息:
typedef void(*VFT)(); //由于类中虚函数的类型都为 void------->函数指针
//用来获取 D 类中由基类 B1 继承而来的虚表信息
void GetB1Vft(B1& pb)
{
cout << "由 B1 继承给 D 的虚表信息" << endl;
VFT* vft = (VFT*)*(int*)(&pb); //获取虚表指针指向的虚函数入口地址
while (*vft) {
(*vft)(); //调用该虚函数
vft++;
}
cout << "====================" << endl;
}
//用来获取 D 类中由基类 B2 继承而来的虚表信息
void GetB2Vft(B2& pb)
{
cout << "由 B1 继承给 D 的虚表信息" << endl;
VFT* vft = (VFT*)*(int*)(&pb); //获取虚表指针指向的虚函数入口地址
while (*vft) {
(*vft)(); //调用该虚函数
vft++;
}
cout << "====================" << endl;
}
由此可见,多继承体系中子类中存在多个虚表信息(由多继承个数决定),并且子类自己新增加的虚函数入口地址存放在第一个继承体系的虚表最后。
小点点:
虚表是在编译时生成的,而并非是运行时生成,但是在运行时调用虚表来实现具体虚函数调用的
虚表是在继承体系中用来存储虚函数入口地址的指针表;
虚基表是在虚拟继承中用来存储偏移量信息的;
ps:
欢迎各位读者评论留言鸭~~