多态的概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
举个例子:比如说买票,普通人是全价买,学生是半价,退伍军人是优先。
多态的定义与实现
多态的构成条件与虚函数
多态很重要的前提就是先继承。
并且要去用基类的指针或者是引用去调用虚函数
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
#include<iostream> using namespace std; class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl; }//成员函数前面加一个virtual就成为虚函数 }; class Student:public Person { public: //这里是虚函数的 重写/覆盖 virtual void BuyTicket() { cout << "买票-半价" << endl; }//条件是三同:返回值和函数名还有参数相同 }; int main() { Person s1; Student s2; Person* p = &s1; p->BuyTicket(); p = &s2; p->BuyTicket(); return 0; }
这里也叫做多态调用。
之前的调用都是普通调用,一直都和对象的类型有关。
多态调用是跟指向的对象有关。
如果改成普通调用就是类型是谁就去调用谁的成员函数,多态调用就是指向的对象是谁就去调用谁的虚函数。
虚函数的重写
子类虚函数可以不加virtual:
#include<iostream> using namespace std; class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl; } }; class Student:public Person { public: //这里是虚函数的 重写/覆盖 void BuyTicket() { cout << "买票-半价" << endl; }//只要三同,子类不加virtual也是虚函数 } int main() { Person s1; Student s2; Person* p = &s1; p->BuyTicket(); p = &s2; p->BuyTicket(); return 0; }
不过这里建议都加上virtual。
协变:
三同中,返回值可以不同,但是要求返回值必须是一个父子类关系的指针或者引用。
#include<iostream> using namespace std; class Person { public: virtual Person* BuyTicket() { cout << "买票-全价" << endl; return this; } }; class Student:public Person { public: virtual Student* BuyTicket() { cout << "买票-半价" << endl; return this; } }; int main() { Person s1; Student s2; Person* p = &s1; p->BuyTicket(); p = &s2; p->BuyTicket(); return 0; }
正常运行。
析构函数的重写
#include<iostream> using namespace std; class A { public: ~A() { cout << "delete s1" << endl; delete[] s1; } protected: int* s1 = new int[20]; }; class B :public A { public: ~B() { cout << "delete s2" << endl; delete[] s2; } protected: int* s2 = new int[20]; }; int main() { A a; B b; return 0; }
目前看来确实没什么问题,都是正常调用,来看看如下的情况:
这里导致了内存泄漏,因为析构函数不是虚函数,只能完成普通调用,所以最好在析构面前加一个virtual。
这下子就可以了。
其实子类不加virtual这里更合适,更方便。
所以在实现父类的时候,最好无脑的给析构函数加virtual。
C++11 override 和 final
final:
如何实现一个不被继承的类?
C++11提供了一个关键字,类定义的时候加final:
如果放在父类的某个虚函数后面就是不让这个虚函数被重写。
但是这个情况很少见。
override:
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
重载、覆盖(重写)、隐藏(重定义)的对比
抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
#include <iostream> using namespace std; class A//抽象类 { public: virtual void add() = 0 {};//纯虚函数 }; class B:public A { public: void add(){} }; int main() { B s;//但是A仍然不能实例化 return 0; }
这就是说给某个函数必须进行重写。
抽象类一般用于,比如说车,他是一个概念,但是他有自行车,电动车,跑车等等,然后还被分为好多的品牌,所以车必须要分类出来。
接口继承和实现继承:
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。