多态是面向对象编程三大特性之一,通过多态我们可以大大增强代码的复用性,也会使代码整体更紧凑简洁;
多态可以分为静态多态和动态多态两类,
静态多态就比如函数重载或者运算符重载;
动态多态则是派生类和虚函数实现的运行时的多态;(地址软绑定
它们就是因为在函数地址绑定时机不同所以有所区分
动态:运行时确定函数地址;
静态:编译时确定函数地址;
实现多态需要满足两个条件:
1、有继承关系
2、子类重写父类中的虚函数(重写:函数返回值类型 函数名 参数列表 完全一致称为重写)
有了这些,我们就可以使用多态了,使用多态同样需要满足一个条件:
⚠️父类指针或引用指向子类对象
举个例子:
先定义一个父类对象,
class Father {
public:
virtual void eyes() {
cout << "双眼皮" << endl;
}
};
virtual声明的函数会变成一个虚函数,这个就是父类中的虚函数,接下来我们声明两个子类,
class Son1 : public Father {
public:
void eyes() {
cout<< "单眼皮" << endl;
}
};
class Son2 : public Father {
public:
void eyes() {
cout<< "单双眼皮" << endl;
}
};
这里我们声明了两个子类对象,都以公共继承的方式继承了父类的对象,并且都重写了父类中的eyes()函数,这时我们写一个测试函数并在主函数中调用,
void text(Father &v) {
v.eyes();
}
int main () {
Son1 s1;
text(s1);// Father &v = s1
Son2 s2;
text(s2);// Father &v = s2
return 0;
}
输出结果会是:
单眼皮
单双眼皮
在调用过程中父类的引用指向了子类的对象,因为父类的eyes是虚函数且子类重写了该函数,所以该引用就会走子类的eyes,这就是多态的实现;
那么多态的底层原理又是什么呢?
还是刚才的父类,如果我们不加virtual,并计算一下这个父类对象到底占多少空间,我们重写一下代码
class Father {
public:
void eyes() {
cout << "双眼皮" << endl;
}
};
void text01() {
cout << "sizeof Father = " << sizeof(Father) <<endl;
}
如果我们在主函数调用text01,输出为:sizeof Father = 1,即此时的Father只占用一个字节的空间(成员函数eyes不属于类的对象,它们分开存储,所以相当于一个空类占用了1一个字节),
那么加上virtual后呢,当我们加上virtual后再运行就会发现输出变成了:
sizeof Father = 4,这就说明了加上virtual后该类的内容就发生了改变,什么东西占四个字节?整形?单精度浮点型?都不大可能,当然还有一个东西占四个字节,那就是指针,指针始终占四个字节;这里就是一个指针,但是这又是什么类型的指针呢?
⚠️这个指针叫做 :vfptr
v:virtual
f:function
ptr: pointer
直译过来就是:虚函数(表)指针
回到代码,此时的Father类的内部就会生成一个vfptr,它会指向一个虚函数表,即⚠️vftable,
那么vftable又是什么?
vftable内部会记录虚函数的地址,在这个Fathe类中,虚函数表中记录的地址就是:
&Father :: eyes() 意思就是Fathe作用域下的函数eyes()的地址;
⚠️那么子类Son1继承后又是怎样的呢?
如果Son1只单纯的继承Father,并不重写eyes函数,那么它就会把父类中的所有东西继承过来,包括父类中的vfptr和vftable,这时继承过来的的vftable内部依然是
&Father :: eyes()
但是若子类Son1重写了eyes函数,这时子类就会把虚函数表中的内容进行一个覆盖操作,此时的子类中的虚函数表内部会替换为子类的虚函数地址,即在这里子类内部就会发生:
&Father :: eyes() 变成 &Son1 :: eyes() ,即 &Father :: eyes() 就被 &Son1 :: eyes() 给覆盖了,这时子类Son1的虚函数表中就只有 &Son1 :: eyes() 了(Son1的eyes函数地址);
但是父类Father中的虚函数表并没有被覆盖,依然是 &Father :: eyes()
⚠️在发生多态时,当父类的指针或者引用指向子类对象时,例如:
Son1 s1;
Father &father = s1;
当father调用eyes()函数的时候,因为此时的虚函数指针指向的是一个Son1对象,它就会去Son1的虚函数表中找这个eyes函数,这就是在运行阶段发生了动态多态。
🛑总的来说就是
1,我们在父类中写了一个虚函数,使类的内部发生了结构的改变,多了一个虚函数指针(vfptr),指向了一个虚函数表(vftable),虚函数表内部记录了该虚函数的入口地址;
2,当子类重写了这个虚函数后,这个子类中的虚函数表内从父类继承来的虚函数地址就会被子类的函数地址覆盖。
3,在最后父类的指针或者引用指向子类对象时,就会走子类的重写的函数地址,就发生了多态。
ps:这就是多态和其内部的剖析,如果有什么问题欢迎交流!