一.多态的概念
1.多态:多态是⼀个继承关系的下的类对象,去调⽤同⼀函数,产⽣了不同的⾏为。
2.多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。
1>编译时多态(静态多态)主要就是函数重载和函数模板,他们传不同类型的参数就可以调⽤不同的函数,通过参数不同达到多种形态。
2>运⾏时多态(动态多态),具体点就是去完成某个⾏为(函数),传不同的对象就会完成不同的⾏为,就达到多种形态。
二.多态的定义及实现
1.实现多态的两个重要条件
1>必须是基类的指针或引用调用虚函数
注:只有基类的指针或引用才能指向派生类对象
2>被调用的必须是虚函数
注:派生类必须对基类的虚函数重写,才能有不同的函数,才能达到多态的多种效果
2.虚函数
1>定义:类成员函数前⾯加virtual修饰,那么这个成员函数被称为虚函数。
注:⾮成员函数不能加virtual修饰。
class Person
{
public:
virtual void BuyTicket()
{
cout << "成人买票-全价" << endl;
}
};
2>虚函数的重写/覆盖:派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值、类型、函数名字、参数列表完全相同),称派⽣类的虚函数重写了基类的虚函数。
class Person
{
public:
virtual void BuyTicket()
{
cout << "成人买票-全价" << endl;
}
};
class Student:public Person
{
public:
virtual void BuyTicket()
{
cout << "学生买票-半价" << endl;
}
};
注:
1.在重写基类虚函数时,派⽣类的虚函数在不加virtual关键字也可以构成重写,但是该种写法不规范
2.协变:派⽣类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤,派⽣类虚函数返回派⽣类对象的指针或者引⽤时,称为协变。(一种特殊情况,只要能区分这种情况下也属于多态就行了)
3>两个关键字:override和final
(1)override:帮助⽤⼾检测是否重写。
(2)final:如果不想让派⽣类重写这个虚函数,那么可以⽤final去修饰。
4>纯虚函数和抽象类
(1)纯虚函数:在虚函数的后⾯写上=0,则这个函数为纯虚函数,纯虚函数不需要定义实现,只要声明即可
(2)抽象类:包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,如果派⽣类继承后不重写纯虚函数,那么派⽣类也是抽象类。
3.析构函数的重写:基类的析构函数建议写成虚函数的形式
基类的析构函数为虚函数,此时派⽣类析构函数只要定义,⽆论是否加virtual关键字,都与基类的析构函数构成重写。这是因为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成destructor,所以基类的析构函数加了vialtual修饰,派⽣类的析构函数就构成重写。
4.重载/重写/隐藏的联系与区别
三.多态实现的原理
1.虚函数表指针:一个含有虚函数的类中都⾄少都有⼀个虚函数表指针,因为⼀个类所有虚函数的地址要被放到这个类对象的虚函数表中,虚函数表也简称虚表。
2.实现:满⾜多态条件后,底层运⾏时到指向的对象的虚表中确定对应的虚函数的地址,实现了指针或引⽤指向基类调⽤基类的虚函数,指向派⽣类就调⽤派⽣类对应的虚函数。
3.动态绑定与静态绑定
(1)静态绑定:对不满⾜多态条件的函数调⽤是在编译时绑定,即编译时确定调⽤函数的地址,叫做静态绑定。
(2)动态绑定:满⾜多态条件的函数调⽤是在运⾏时绑定,即在运⾏时到指向对象的虚函数表中找到调⽤函数的地址,也就做动态绑定。
4.虚函数表
(1)基类对象的虚函数表中存放基类所有虚函数的地址。
(2)派⽣类由两部分构成,继承下来的基类和⾃⼰的成员,⼀般情况下,继承下来的基类中有虚函数表指针,⾃⼰不会再⽣成虚函数表指针。但是要注意继承下来的基类部分虚函数表指针和基类对象的虚函数表指针不是同⼀个
(3)派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函数地址。
(4)派⽣类的虚函数表中包含:基类的虚函数地址,派⽣类重写的虚函数地址,派⽣类⾃⼰的虚函数地址三个部分。
(5)虚函数表本质是⼀个存虚函数指针的指针数组
(6)虚函数和普通函数⼀样的,编译好后是⼀段指令,都是存在代码段的,只是虚函数的地址⼜存到了虚表中。
(7)虚表存放在哪里没有具体规定,不同编译器有自己的存放位置,经过测试发现存放地址与常量区地址重合度最高