目录
1.多态的概念
2.简单认识
(1)一个案例
(2)多态的两个满足条件
(3)虚函数的重写
(4)两个特殊情况
1.多态的概念
(1)多态就是多种形态;
(2)具体而言:就是不同的对象去完成相同的任务的时候,产生的效果是不一样的,产生了不同的形态;
2.简单认识
(1)一个案例
#include<iostream>
using namespace std;
class person
{
public:
virtual void buyticket()
{
cout << "person成人票---全价" << endl;
}
};
class student :public person
{
public:
virtual void buyticket()
{
cout << "student学生票--半价" << endl;
}
};
void func(person& p)
{
p.buyticket();
}
int main()
{
person p1;
student st1;
func(p1);
func(st1);
return 0;
}
我们这个就是拿的当代大学生乘坐动车和高铁可以获得折扣的例子演示的,我们定义了两个类,这两个类之间就是普通的继承关系,student继承person类,两个类里面都有一个虚函数,就是以virtual开头的函数,我们分别定义了两个对象,两个对象分别作为函数的参数传递进去,我们可以发现,当我们传递的参数是父类的对象的时候,我们会发现这个打印的结果就是成人票,我们传递的参数是子类对象的时候,这个打印的结果就是学生票,就是传递进去不同的参数,这个打印的结果就是不一样的,像这种我们把不同的对象,完成相同的任务(在这个里面就是去调用buyticket函数),出现不同的结果的现象,我们称之为多态;
(2)多态的两个满足条件
第一个就是虚函数的重写,第二个就是通过父类的指针或者是引用进行调用虚函数,这个指针或者是引用必须是父类的,因为这个如果是子类的话,他就是只能接受子类传递过来的对象,不能接受传递的父类的对象;
但是如果是父类的指针或者是引用的话,我们可以传递父类对象,也可以传递子类的对象,就算是传递的子类的对象,我们也是可以只把子类里面属于父类的成员函数和成员变量传递过去,这个就是复制兼容性规则,也就是我们常说的切片,在两个满足继承关系的情况下,这个是允许的;
(3)虚函数的重写
虚函数的重写也是需要满足三个条件的,都是针对这个父类和子类的函数的,父类和子类的虚函数要求满足函数的名字相同,函数的参数相同而且函数的返回值相同,这三个条件就是构成虚函数重写的条件,而虚函数的重写,又是构成多态的条件;
virtual只能进行修饰成员函数,否则(virtual修饰其他的非成员函数)就会报错;
(4)两个特殊情况
协变:虚函数的返回值可以不一样,但是要求必须是父类或者是子类的指针或者是引用;
下面就是这个特殊情况的举例,在这个函数的返回值是父类的引用或者是指针的时候,这个时候的函数的返回值虽然是不一样的,但是这个依然可以构成虚函数的重写;
class person
{
public:
virtual person* buyticket()
{
cout << "person成人票---全价" << endl;
return nullptr;
}
};
class student :public person
{
public:
virtual student* buyticket()
{
cout << "student学生票--半价" << endl;
return nullptr;
}
};
第二种特殊情况就是这个子类的虚函数可以不在这个函数的前面添加上virtual,这个其实是很重要的,为什么要这么进行设计,其实是和这个析构函数有一些关系的;
我们之前介绍过,因为多态的原因,这个在析构函数的执行会被进行特殊的处理,就是全部转化成为destructor(这个具体是什么我们后面还是会进行学习的);
下面我们给上面的这个案例的代码添加上析构函数,来解释一下为什么这个子类的虚函数的前面可以不用添加上virtual关键字;
这个运行结果也是没有问题的,因为我们执行完成之后进行对象的析构,p1是父类的对象就是调用父类的析构函数,s1是子类的对象就会先调用子类的析构函数,子类的析构函数执行完毕之后就会自动调用父类的析构函数(上一次我们在继承里面已经介绍过了,这个是在继承的前提下面默认进行的,而且是必须按照这个顺序,因为我们的子类继承了父类的成员变量或者是成员函数,我们先析构父类,如果这个子类里面使用就会造成野指针的问题,我们先析构子类,在析构父类的时候,父类是没有收到任何影响的,所以析构的顺序就是先子类后父类);
我们接下来换一种情况进行演示:
父类的指针可以指向父类的对象,也可以指向子类的对象,但是执行析构函数的时候,调用析构函数的时候是根据这个指针的类型调用的,所以两个析构都是调用的父类的构造函数,但是我们想要第二次调用子类的析构函数,这个时候我们就需要在这个析构函数的前面添加上virtual,就可以正确调用析构函数;
但是,如果我们忘记写这个virtual,我们只需要在这个父类析构函数前面加上virtual,我们在子类里面不添加virtual也可以完成这个正常的调用;
这个就是在我们忘记写子类的析构函数的情况下,只要我们把这个父类的析构函数前面加上virtual,我们这个子类里面不添加virtual也是可以正常调用的,这个就是上面的第二个特殊情况为这个情景开了一个绿灯,如果我们忘记写了,不会影响这个程序的执行;
但是,对于上面的这两个特殊情况,我们只需要了解即可,在我们平常去写代码的话,我们还是规范书写,减少这些不必要的麻烦;