生活中我们不但有父与子的继承关系,还有 面对一种事物 不同行为的多态关系。比如对于售票而言,普通人去则是排队全价买票,但是对于学生而言,则会有半价优惠,对于军人而言则会有军人优先通道 。
构成多态的条件:
虚函数的重写:
提到多态就不得不提及虚函数,这时我们又得引入一个熟悉的关键字——“virtual”。没错,这与我们在学习继承时解决菱形继承产生二义性问题时所用的“虚继承”关键字一样。不过 这里我们用它来标识虚函数。
虚函数重写的三大条件:(缺一不可)
![](https://i-blog.csdnimg.cn/blog_migrate/a1de2b6018844211a6b9cbd8bd4e330a.png)
上面我们可以看到,父类 调用了父类的虚函数,子类调用了子类的虚函数。
重写的特例——协变、析构函数的重写
协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
![](https://i-blog.csdnimg.cn/blog_migrate/65251a1fae063f31efba11cda099e81f.png)
Student类并未加关键字,但是依旧构成虚函数重写。值得注意的是
若把虚函数的返回值父类和子类交换则不构成协变。
析构函数的重写:
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字, 都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同, 看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处 理,编译后析构函数的名称统一处理成destructor。
如图所示,我们在使用父类指向子类new出的空间时,若未完成析构函数的重写,则会调用父类析构,而父类析构显然不能删除new出的子类空间,则会报错。所以建议父类的析构函数写成虚函数类型。
重写,重载,重定义——
关于虚表
这里我们对比a,b发现,b除了成员变量_b外,还存储了一个函数指针,对象中的这个指针我们叫做虚函数表指针。 对比A,B,C大小时候我们发现,虽然C中虽然为空类,但他依旧可以被实例化,所以我们所给大小为1,而A,B,我们可以看到B中显然多了一个函数指针,所以比A大4个字节(32位系统下)。
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是 他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
1.基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
若完成重写,则子类和父类共享该虚表
这里我们可以看到,func1完成了重写,所以被放进虚表中且对象 d 虚表中func1为重写的函数,可理解为将原虚表func1覆盖。而func2未完成重写,但是加了virtual关键字,因此也被放入虚表,不过与父类为同一函数。func3没有virtual关键字,所以只是单纯继承下来。
抽象类与纯虚函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。