`​正文开始​`

## 二、多态的定义及实现

### 2.1多态的构成条件

1.必须通过基类的指针或者引用去调用虚函数！
2.被调用的函数必须是虚函数，且派生类必须对基类的虚函数进行重写！

### 2.2 虚函数

`class Person{public:  //虚函数  virtual void BuyTicket(){}};`

### 2.3虚函数的重写

`class Person{public:  Person(const char* name)    :_name(name)  {}  //虚函数  //虚函数+函数名/参数/返回值->重写/覆盖  virtual void BuyTicket()  {    cout <<_name<< "-Person:买票-全价 100 ￥" << endl;  }protected:  string _name;  //int _id;};class Student :public Person{public:  Student(const char* name)    :Person(name)  {}  //虚函数+函数名/参数/返回值->重写/覆盖  virtual void BuyTicket()  {    cout << _name << "-Student:买票-半价 50 ￥" << endl;  }};class Soldier :public Person{public:  Soldier(const char* name)    :Person(name)  {}  //虚函数+函数名/参数/返回值->重写/覆盖  virtual void BuyTicket()  {    cout << _name << "-Soldier:优先买预留票-买票-全价 100 ￥" << endl;  }};`

1. 协变(基类与派生类虚函数返回值类型不同)

`class Person {public:  virtual A* f() {  return new A;}};class Student : public Person {public:  virtual B* f() {  return new B;}};`

2. 析构函数的重写(基类与派生类析构函数的名字不同)

`class Person {public:  virtual ~Person() { cout << "~Person()" << endl; }};class Student : public Person {public:  virtual ~Student() { cout << "~Student()" << endl; }};// 只有派生类Student的析构函数重写了Person的析构函数，下面的delete对象调用析构函数，才能构成多态，才能保证p1和p2指向的对象正确的调用析构函数。int main(){  Person* p1 = new Person;  Person* p2 = new Student;  delete p1;  delete p2;  return 0;}`

### 2.4 C++11 override 和 final

1. final：修饰虚函数，表示该虚函数不能再被重写

`class Car{public:  virtual void Drive() final {}};class Benz :public Car{public:  virtual void Drive() { cout << "Benz-舒适" << endl; }};`

2. override: 检查派生类虚函数是否重写了基类某个虚函数，如果没有重写编译报错。

`class Car {public:  virtual void Drive() {}};class Benz :public Car {public:  virtual void Drive() override { cout << "Benz-舒适" << endl; }};`

## 三、抽象类

### 3.1 概念

`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;  }};void Test(){  //Car car;  Car* pBenz = new Benz;  pBenz->Drive();  Car* pBMW = new BMW;  pBMW->Drive();}int main(){  Test();  return 0;}`

## 四、多态的原理

### 4.1虚函数表

`// 这里常考一道笔试题：sizeof(Base)是多少？class Base{public:  virtual void Func1()  {    cout << "Func1()" << endl;  }private:  int _b = 1;};int main(){  cout << sizeof(Base) << endl;  return 0;}`

`// 针对上面的代码我们做出以下改造// 1.我们增加一个派生类Derive去继承Base// 2.Derive中重写Func1// 3.Base再增加一个虚函数Func2和一个普通函数Func3class Base{public:  virtual void Func1()  {    cout << "Base::Func1()" << endl;  }  virtual void Func2()  {    cout << "Base::Func2()" << endl;  }  void Func3()  {    cout << "Base::Func3()" << endl;  }private:  int _b = 1;};class Derive : public Base{public:  virtual void Func1()  {    cout << "Derive::Func1()" << endl;  }private:  int _d = 2;};int main(){  Base b;  Derive d;  return 0;}`

1. 派生类对象d中也有一个虚表指针，d对象由两部分构成，一部分是父类继承下来的成员，虚表指针也就是存在部分的另一部分是自己的成员。
2. 基类b对象和派生类d对象虚表是不一样的，这里我们发现Func1完成了重写，所以d的虚表中存的是重写的Derive::Func1，所以虚函数的重写也叫作覆盖，覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法，覆盖是原理层的叫法。
3. 另外Func2继承下来后是虚函数，所以放进了虚表，Func3也继承下来了，但是不是虚函数，所以不会放进虚表。
4. 虚函数表本质是一个存虚函数指针的指针数组，一般情况这个数组最后面放了一个nullptr。
5. 总结一下派生类的虚表生成：a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数，用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
6. 这里还有一个童鞋们很容易混淆的问题：虚函数存在哪的？虚表存在哪的？ 答：虚函数存在虚表，虚表存在对象中注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针，不是虚函数，虚函数和普通函数一样的，都是存在代码段的，只是他的指针又存到了虚表中。另外对象中存的不是虚表，存的是虚表指针。那么虚表存在哪的呢？实际我们去验证一下会发现vs下是存在代码段的，Linux g++下大家自己去验证？

### 4.2多态的原理

3. 这样就实现出了不同对象去完成同一行为时，展现出不同的形态。
4. 反过来思考我们要达到多态，有两个条件，一个是虚函数覆盖，一个是对象的指针或引用调用虚函数。反思一下为什么？
5. 再通过下面的汇编代码分析，看出满足多态以后的函数调用，不是在编译时确定的，是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

`//以下汇编代码中和这个问题不相关的都被去掉了void Func(Person* p) {   p->BuyTicket();  // p中存的是mike对象的指针，将p移动到eax中00482511  mov         eax,dword ptr [p]  // [eax]就是取eax值指向的内容，这里相当于把mike对象头4个字节(虚表指针)移动到了edx00482514  mov         edx,dword ptr [eax] // [edx]就是取edx值指向的内容，这里相当于把虚表中的头4字节存的虚函数指针移动到了eax 00482516  mov         esi,esp  // call eax中存虚函数的指针。这里可以看出满足多态的调用，不是在编译时确定的，是运行起来以后到对象的中取找的。0048251D  call        eax  0048251F  cmp         esi,esp  }int main(){//... // 首先BuyTicket虽然是虚函数，但是mike是对象，不满足多态的条件，所以这里是普通函数的调用转换成地址时，是在编译时已经从符号表确认了函数的地址，直接call 地址  mike.BuyTicket();004823B3  lea         ecx,[mike]  004823B6  call        Person::BuyTicket (0481479h)    return 0;004823BB  xor         eax,eax  }`

### 4.3 动态绑定与静态绑定

1. 静态绑定又称为前期绑定(早绑定)，在程序编译期间确定了程序的行为，也称为静态多态，比如：函数重载
2. 动态绑定又称后期绑定(晚绑定)，是在程序运行期间，根据具体拿到的类型确定程序的具体行为，调用具体的函数，也称为动态多态。

（本章上完！）