多态的概念
概念
多态即多种形态,当不同对象去执行同一个行为时,会产生不同的状态
多态的定义及实现
多态的构成条件
必须通过基类的指针/引用去调用虚函数( virtual所修饰)
被调用的函数必须是虚函数,并且派生类必须对基类的虚函数进行重写
class Person { public: virtual void Buyticket() { cout << "Person:买票-全价" << endl; } }; class Student:public Person { public: virtual void Buyticket() { cout << "Student:买票-半价" << endl; } }; //多态调用 void Display(Person& p) { p.Buyticket(); } int main() { Person pn; Display(pn); Student st; Display(st); return 0; }
普通调用:和调用对象的类型有关
多态调用:和指针/引用指向的对象有关
class Person { public: virtual void Buyticket() { cout << "Person:买票-全价" << endl; } }; class Student:public Person { public: virtual void Buyticket() { cout << "Student:买票-半价" << endl; } }; //普通调用 void Display(Person p) { p.Buyticket(); } int main() { Person pn; Display(pn); Student st; Display(st); return 0; }
虚函数
虚函数:被 virtual修饰的类成员函数称作虚函数
class Person { public: virtual void Buyticket() { cout << "Person:买票-全价" << endl; } };
虚函数的重写
虚函数的重写:派生类中有一个和基类完全相同的虚函数,称派生类的虚函数重写了基类的虚函数
虚函数重写的两个例外
协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。也就是基类虚函数返回基类对象的指针/引用,派生类虚函数返回派生类对象的指针/引用
class Person { public: ~Person() { cout << "~Person()" << endl; delete[] _p; } protected: int* _p = new int[10]; }; class Student :public Person { public: ~Student() { cout << "~Student()" << endl; delete[] _s; } protected: int* _s = new int[10]; }; int main() { Person pn; Student st; return 0; }
析构函数的重写(基类与派生类析构函数名不同)
先观察如果析构函数不设置为虚函数
class Person { public: ~Person() { cout << "~Person()" <<endl; delete[] _p; } protected: int* _p = new int[10]; }; class Student :public Person { public: ~Student() { cout << "~Student()" << endl; delete[] _s; } protected: int* _s = new int[20]; }; int main() { Person pn; Student st; return 0; }
运行起来似乎没有什么问题,做如下修改,结果又是如何
int main() { Person* ptr1 = new Person; Person* ptr2 = new Student; delete ptr1; delete ptr2; return 0; }
由运行结果可知,发生了内存泄漏,Student的资源并没有释放;delete的行为是:使用指针调用析构函数,由于是普通调用,函数与调用对象的类型有关,所以造成了内存泄漏;如果将函数设置为虚函数结构会怎么样呢?
class Person { public: virtual ~Person() { cout << "~Person()" << endl; delete[] _p; } protected: int* _p = new int[10]; }; class Student :public Person { public: virtual ~Student() { cout << "~Student()" << endl; delete[] _s; } protected: int* _s = new int[20]; };
内存泄漏的问题完美地解决
虽然函数名不同,这里其实是构成了虚函数的重写,编译器对析构函数的名称做了处理,编译后的析构函数的名称统一处理成 destructor
子类虚函数可以不加 virtual修饰(不推荐)
C++11override和final
final:修饰虚函数,表示该虚函数不能被重写
override:检查派生类虚函数是否重写了某类的某个虚函数,如果没有重写编译报错
重载,覆盖,隐藏的对比
抽象类
概念
在虚函数的后面加上=0,称这个函数为纯虚函数,包含纯虚函数的类称作抽象类,抽象类不能实例化对象;派生类继承后也不能实例化对象,只有重写纯虚函数,派生类才能实例化对象;纯虚函数规定了派生类必须重写
class Car { public: virtual void Drive()=0; }; int main() { Car c; return 0; }
纯虚函数不可以实例化
class Car { public: virtual void Drive() = 0; }; class NIO : public Car { public: virtual void Drive() { cout << "安全驾驶" << endl; } }; int main() { NIO et7; et7.Drive(); return 0; }
接口继承和实现继承
普通函数的继承是是实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现;虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,构造多态,继承的是接口