一、多态的概念
多态的概念:联想一下生活实际,不同的人去完成相同的事,是会有不同的结果的,就比如买火车票,成年人要购买全票,学生可以购买半票,军人等可以优先买票。同理,C++为了更加贴合人类实际,就构建了多态,在去完成某个行为的时候,不同的对象去完成的时候,会产生不同的状态。
二、多态的定义和实现
2-1多态构成的条件
多态是在不同的继承关系的类的对象里面,调用同一个函数,产生了不同的行为,比如学生继承了Person类,那么在调用去买票这一个函数时,Person类对象要买全票,Student类去购买半票。
同时,在继承中要构成多态还需要两个条件
1.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(重写时不同于重载和隐藏的,重写的要求更加严苛,不仅要求函数名相同,返回值和参数列表也要相同)
2.调用虚函数的时候,必须使用基类的引用或者指针
举个例子:
(虚函数的关键字是virtual)
在此中,有人可能会疑惑,Func()和func()里面不都是Person 吗,为什么传入为Student时会调用的时Student中的Buytickets()函数,而不是父类的,因为这里使用了多态,对应传入的对象是谁,那么就调用谁的相关功能。
2-2 析构函数的重写
如果基类的析构函数时虚函数,那么对于继承基类的派生类中的析构函数不管是否有加上virtual,都是虚函数,与基类中的析构函数构成重写,有人会疑惑,都没有加virtual,为什么就是虚函数了?析构的函数名都不同,怎么就构成了重写?首先,解决第一个疑惑,因为派生类是继承了基类的,只要基类中有虚函数(也就是写了virtual),那么子类中与其构成重写的函数就保留有虚函数的特性(但是反过来就不成立了),其次,析构函数名虽然表面是不相同的,但是底层是处理为相同的名字,即destructor,所以对于继承中的析构函数是构成重写的。
举一个例子:
如果不使用virtual,虚拟函数会导致什么后果
结果是只调用了Person()类中的析构,并没有调用Student()中的析构,导致了内存泄露,所以是需要使用virtual来先调用子类的析构再来调用父类的析构,让他变成多态的形式。
三、重载,重写(覆盖),重定义(隐藏)三者的区别
重载:两个函数在同一个类的作用域里面,返回值或者参数列表不同
重写:两个函数分别在基类和派生类里面,均为虚函数,函数名相同,返回值,参数列表均一致(除了协变)
重定义:两个函数分别在基类和派生类里面,函数名相同,且两个同名函数不构成重写就是重定义
四、抽象类
在虚函数后面写上=0,则这个函数就变为纯虚函数。包含有纯虚函数的类称为抽象类(也叫接口类),抽象类是不可以实例化出对象的,对于继承了抽象类的子类也是无法实例化对象,如果要实例化对象,就需要重写纯虚函数。
举个例子:
五、多态的原理
5-1 虚函数表
先看个例子:
此处在A 和B类中都没有定义成员变量,为什么变量却有八字节的大小,因为类中包含了虚函数,虚函数是放置在了虚表中,而虚表就是一个指针数组,用来存放每个虚函数的指针,而虚表的调用是利用虚表指针,而着八字节的大小就是用来存放虚表指针的,在64位下,指针的大小是八字节,在32位下,指针的大小是四字节。
再来看一个例子:
在Person和Student类中分别包含了一个虚表指针,并指向了自己对应的虚表,Student类中包含的虚表是由两部分组成,一部分是继承父类中的虚函数的指针,另一部分是子类自己的成员,对于同名的函数,在编译的时候,子类中的虚表只是将父类中的虚函数指针拷贝下来,而在运行的时候,才会将重写的同名函数进行覆盖,覆盖掉其原本的地址,传入其重写后的地址,从而实现了覆盖。
Person类中的func3()也会被继承下来,但是由于其并不是虚函数,所以不会放入到需表中,而是在编译的时候就确定了程序行为,也就是确定了这个函数的调用地址。
那么,思考一下,虚表是存放在了哪里?虚函数又是存放在哪里?有人会误认为,虚表就是存放成员对象中,而虚函数就是存放在需表中,但是,实际上并不是这样的,虚表只是用来存放虚函数指针的一个数组,其中并不存放着虚函数的定义和声明,而虚函数和其他普通函数一样,都是存放在代码段的,用于程序的运行;而对于虚表,它是存放在虚表指针当中的,虚表指针在VS下是存放在对象的前八个字节的(在64位下),它是存放在常量区的,因为同一个类的对象是调用同一个虚表的,如果将虚表存放在栈上,就会导致每次都要创建和销毁,但是存放在常量区就不一样了,可以创建一次,多个同类对象使用,等到生命周期结束后再销毁。
5-2 多态的原理
还是上面的例子
在传入Person类的对象时,调用的时Person类中的Buytickets(),而传入的是Student类对象时,调用的是Student()类中的函数,从而实现了不同的对象调用又不同的行为和结果这个多态目的。而实现多态为什么要利用的是父类的指针或者引用?因为子类传递给父类才可以发生切片,也就是父类的指针和引用指向的是子类中的父类的部分,而传入的是父类对象的时候,那么指向的自然而然就是父类的指针。那为什么要虚函数?因为虚函数中的虚表才可以完成重写,实现在运行的时候,将重写的虚函数覆盖,也就是在调用子类虚函数的时候,将子类中的虚表中的构成重写的父类虚函数的地址更换成重写后的子类的虚函数,从而使得调用的时候,是调用子类的重写函数。实现不同对象,对应不同行为。
多态的分享大体就这么多啦,希望对你们有所帮助。