)
一、多态的概念多态就是多种状态比如打印机可以打印黑白 也可以打印彩的买票买学生票或者成人票或者儿童票也可以逃票~~~二、多态的定义和实现实现多态的两个必要条件必须是基类的指针或者引⽤调⽤虚函数被调⽤的函数必须是虚函数并且完成了虚函数重写/覆盖。如下是代码#include iostream using namespace std; class Person { public: virtual void BuyTicket() { cout 买票-全价 endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout 买票-打折 endl; } }; void Func(Person* ptr) { // 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket // 但是跟ptr没关系⽽是由ptr指向的对象决定的。 ptr-BuyTicket(); } int main() { Person ps; Student st; Func(ps); Func(st); return 0; }那啥是重写和覆盖呢虚函数的重写/覆盖派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值 类型、函数名字、参数列表完全相同)称派⽣类的虚函数重写了基类的虚函数。注意在重写基类虚函数时派⽣类的虚函数在不加virtual关键字时虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性)但是该种写法不是很规范不建议这样使⽤不过在考试选择题中经常会故意买这个坑让你判断是否构成多态。class Student : public Person { public: private: virtual void BuyTicket() { cout 买票-打折 endl; } };甚至这样也是可以调用的是因为多态发生派生类使用的是基类的声明 所以private还是public就无所谓了~重写的一些其他问题 协变派⽣类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引⽤派⽣类虚函数返回派⽣类对象的指针或者引⽤时称为协变。协变的实际意义并不⼤所以我们了解⼀下即可。class A { }; class B : public A { }; class Person { public: virtual A *BuyTicket() { cout 买票-全价 endl; return nullptr; } }; class Student : public Person { public: virtual B *BuyTicket() { cout 买票-打折 endl; return nullptr; } }; void Func(Person *ptr) { ptr-BuyTicket(); } int main() { Person ps; Student st; Func(ps); Func(st); return 0; }析构函数的重写因为多态的原因析构函数的名字变成了destructor() 这样两个类的继承的析构就变成了隐藏class A { public: virtual ~A() { cout ~A() endl; } }; class B : public A { public: ~B() { cout ~B()-delete: _p endl; delete _p; } protected: int *_p new int[10]; }; // 只有派⽣类Student的析构函数重写了Person的析构函数下⾯的delete对象调⽤析构函数才能 // 构成多态才能保证p1和p2指向的对象正确的调⽤析构函数。 int main() { A *p1 new A; A *p2 new B; delete p1; delete p2; return 0; }这样才不会造成内存泄漏 多态调用指向谁调用谁的析构函数基类的析构函数一定要是虚函数重载 重写 隐藏的对比纯虚函数和抽象类在虚函数的后⾯写上 0 则这个函数为纯虚函数纯虚函数不需要定义实现(实现没啥意义因为要被派⽣类重写但是语法上可以实现)只要声明即可。包含纯虚函数的类叫做抽象类抽象类不能实例化出对象如果派⽣类继承后不重写纯虚函数那么派⽣类也是抽象类。纯虚函数某种程度上强制了派⽣类重写虚函数因为不重写实例化不出对象。纯虚函数1. 不能实例化对象2. 强制子类去重写虚函数class Car { public: virtual void Drive() 0; }; class Benz :public Car { public: virtual void Drive() { cout Benz-舒适 endl; } }; class BMW :public Car { public: virtual void Drive() { cout BMW-操控 endl; } }; int main() { // 编译报错error C2259: “Car”: ⽆法实例化抽象类 Car car; Car* pBenz new Benz; pBenz-Drive(); Car* pBMW new BMW; pBMW-Drive(); return 0; }三、多态的原理看下列代码运行结果是什么64位class Base { public: virtual void Func1() { cout Func1() endl; } // virtual void Func2() // { // cout Func2() endl; // } protected: int _b 1; char _ch x; }; int main() { Base b; cout sizeof(b) endl; return 0; }因为会存一个虚函数表指针 64位 是8个字节虚函数表简称虚表本质是函数指针数组class Person { public: virtual void BuyTicket() { cout 买票-全价 endl; } private: string _name; }; class Student : public Person { public: virtual void BuyTicket() { cout 买票-打折 endl; } private: string _id; }; class Soldier : public Person { public: virtual void BuyTicket() { cout 买票-优先 endl; } private: string _codename; }; void Func(Person* ptr) { // 这里可以看到虽然都是Person指针ptr在调用BuyTicket // 但是跟ptr没关系而是由ptr指向的对象决定的。 ptr-BuyTicket(); } int main() { // 其次多态不仅仅发生在派生类对象之间多个派生类继承基类重写虚函数后 // 多态也会发生在多个派生类之间。 Person ps; Student st; Soldier sr; Func(ps); Func(st); Func(sr); return 0; }满足多态运行时到指向对象虚函数表中找到对应虚函数调用3.1 动态绑定与静态绑定对不满⾜多态条件(指针或者引⽤调⽤虚函数)的函数调⽤是在编译时绑定也就是编译时确定调⽤函数的地址叫做静态绑定。满⾜多态条件的函数调⽤是在运⾏时绑定也就是在运⾏时到指向对象的虚函数表中找到调⽤函数的地址也就做动态绑定。3.2 虚函数表同一个类的所有对象共享同一份虚函数表同时派生类会继承基类的虚函数表然后根据已有的进行覆盖和添加派⽣类的虚函数表中包含(1)基类的虚函数地址(2)派⽣类重写的虚函数地址完成覆盖派⽣类⾃⼰的虚函数地址三个部分。虚函数存在哪的虚函数和普通函数⼀样的编译好后是⼀段指令都是存在代码段的只是虚函数的地址⼜存到了虚表中。虚函数表存在哪的这个问题严格说并没有标准答案C标准并没有规定我们写下⾯的代码可以对⽐验证⼀下。vs下是存在代码段(常量区)