目录
1. 多态的定义及实现
1.1继承中的多态
1.2虚函数的特殊情况
2. 抽象类
3.C++11关键词override 和 final
4.重载、覆盖/重写、隐藏/重定义的对比
1. 多态的定义及实现
🍪什么是多态?
- C++ 中,多态(Polymorphism)是面向对象编程的重要特性之一。
- C++ 多态允许使用基类指针或引用来调用子类的重写方法,从而使得同一接口可以表现不同的行为。
多态通俗来说就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
🌰举个例子:
定义一个类Person,类中定义一个方法"吃饭",Chinese和British继承Person,同样是调用人“吃饭”的方法,Chinese用筷子吃饭,British用刀叉吃饭,对于不同的人调用该方法应该有不同的结果。
1.1继承中的多态
🍪在继承体系中构成多态的两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
🍪虚函数与虚函数的重写:
- 虚函数:virtual修饰的类成员函数
- 虚函数的重写/覆盖:派生类中有一个跟基类完全相同的虚函数(返回值类型、函数名、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
以上面的吃饭方式为例:
class Person
{
public:// 虚函数virtual void HaveMeal() { cout << "Person --> 工具吃饭" << endl; }};
class Chinese : public Person
{
public:// 对基类虚函数的重写virtual void HaveMeal() { cout << "Chinese --> 筷子吃饭" << endl; }};
class British : public Person
{
public:virtual void HaveMeal() { cout << "British --> 刀叉吃饭" << endl; }};
🍪总结:
- 普通调用:跟调用对象的类型有关
- 多态调用:跟指向的对象有关(基类的指针和引用,实际上指向的是派生类对象中基类的部分,仍属于派生类对象)
1.2虚函数的特殊情况
🍪虚函数的三种特殊情况
🌸子类函数可以不加virtual 继承父类的虚函数进行重写
📖Note:
- 一般建议父类子类虚函数都显式加virtual
class Person
{
public:// 虚函数virtual void HaveMeal() { cout << "Person --> 工具吃饭" << endl; }};
class Chinese : public Person
{
public:// 基类虚函数的重写:不加virtual修饰void HaveMeal() { cout << "Chinese --> 筷子吃饭" << endl; }};
class British : public Person
{
public:void HaveMeal() { cout << "British --> 刀叉吃饭" << endl; }};
🌸🌸协变:虚函数重写时,返回值可以不同,但是要求返回值必须是一个父子类关系的指针或者引用
📖Note:
- 只需要满足父子关系即可:如B类继承自A类,则Person中的虚函数返回A*,Chinese中的虚函数返回B*也是满足条件的
class Person
{
public:// 虚函数virtual Person* HaveMeal() { cout << "Person --> 工具吃饭" << endl; return this; }};
class Chinese : public Person
{
public:// 基类虚函数的重写:不加virtual修饰Chinese* HaveMeal() { cout << "Chinese --> 筷子吃饭" << endl; return this;}};
class British : public Person
{
public:British* HaveMeal() { cout << "British --> 刀叉吃饭" << endl; return this;}};
🌸🌸🌸实现父类的函数,一般给析构函数加上virtual 构成多态调用
下例中:Person中有一个new出来的成员,Student类继承自Person类,也有new出来的一个成员,基类和派生类有各自的析构函数进行delete
class Person
{
public:~Person(){cout << "delete-Person:" << _p << endl;delete[] _p;}
protected:int* _p = new int[10];
};class Student : public Person
{
public:~Student(){cout << "delete-Student:" << _s << endl;delete[] _s;}
protected:int* _s = new int[20];};
分析以下代码的问题
int main()
{Person* ptr1 = new Person;Person* ptr2 = new Student;delete ptr1;delete ptr2;return 0;
}
对于创建的两个指针变量,ptr1指向Person对象,ptr2指向Student对象中Person对象的切片
当delete这两个指针时:
- delete ptr1:毫无疑问会调用Person的析构函数,释放ptr1指向的空间
- delete ptr2:实际上调用的也是Person的析构函数,因为普通函数的调用,只和调用对象的类型有关,这里是Person类型的对象调用析构函数,所以只会释放Student对象中Person对象部分的空间,而Student对象的空间没有被释放,造成了内存泄漏
🍪解决方案:析构函数定义为虚函数,使这里的调用为多态调用
📖Tips:
- 定义基类时,将基类的析构函数使用virtual修饰,可以避免不必要的内存泄漏,虚函数重写时,派生类中的虚函数修饰词virtual可以省略,所以只需要基类的析构函数使用virtual修饰即可。
- 基类和派生类的析构函数都会被编译器解析成destructor,所以基类和派生类的析构函数可以构成重写
class Person
{
public:virtual ~Person(){cout << "delete-Person:" << _p << endl;delete[] _p;}
protected:int* _p = new int[10];
};class Student : public Person
{
public:~Student(){cout << "delete-Student:" << _s << endl;delete[] _s;}
protected:int* _s = new int[20];};
2. 抽象类
纯虚函数:在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
抽象类:包含纯虚函数的类叫做抽象类/接口类
🍪抽象类的特点:
- 抽象类不能实例化出对象。
- 派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
class Major
{
public:// 纯虚函数virtual void Print() = 0;
};
class Automation :public Major
{
public:// 重写纯虚函数virtual void Print(){cout << "Automation ---> 自动化专业" << endl;}
};
class English :public Major
{
public:virtual void Print(){cout << "English --> 英语专业" << endl;}
};
🍪接口继承和实现继承
- 普通函数的继承是一种实现继承:派生类继承了基类函数,可以使用函数,继承的是函数的实现。
- 虚函数的继承是一种接口继承:派生类继承的是基类虚函数的接口,目的是为了重写,达成 多态,因此如果不实现多态,不要把函数定义成虚函数。
3.C++11关键词override 和 final
final:修饰虚函数,表示该虚函数不能再被重写
📖Tips:
- 类定义时加final,表示该类是一个最终类,不能被继承
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Major
{
public:virtual void Print() {};
};
class Automation :public Major
{
public:// 重写纯虚函数virtual void Print() override{cout << "Automation ---> 自动化专业" << endl;}
};
class English :public Major
{
public:virtual void Print() override{cout << "English --> 英语专业" << endl;}
};
4.重载、覆盖/重写、隐藏/重定义的对比
📖Tips:
- 两个基类和派生类的同名函数不构成重写就是重定义