一、虚机制的引入
利用动态编联实现——虚函数来解决上述问题
二、虚函数
- 类中只要有其他的虚函数,析构函数就应该定义成虚函数。
- 若基类中析构函数为虚函数,则派生类中的析构函数不论写不写virtual关键字都是虚函数。
- 派生类中新的虚函数应尽量避免与基类中的虚函数重名。若重名,则写不写virtual关键字都是虚函数。
- 相同或相容:返回类型与基类相同或互为父类和子类的关系。
三、虚函数表
1、引入:
2、定义:
- 说明中的第一点,虚函数可以是自己定义的也可以是从基类中继承的
- 说明中的第四点,派生类中的前几个,顺序与基类中定义的函数顺序相同,然后才是派生类中自己定义的函数。
- 若在基类的Show()函数前加上virtual关键字,运行结果不变
- vptr虚拟表指针,指向虚拟表。
四、虚机制的作用机制
1、
需要注意的是对象型和指针、引用型不同,静态、动态一致!(本题中即为Parent类型),因为是子类向父类转化,已经过裁剪。
- sh1静态类型为Shape类型,动态类型为矩形;psh2静态类型为Shape指针类型,动态类型为圆的指针类型。
- Func可能不被调用,因为通过虚拟表找到的未必就是Func函数。
EX1:
- 标红的代码部分,需注意p1、p2的静态类型都是A类,而在A类中是找不到f1()的,因此编译报错。
EX2:
- Date类析构函数加virtual关键字的原因:如果不加virtual关键字则不会采用虚机制,在delete pDate1的时候就不会调用派生类的析构函数,而是直接释放基类的析构函数。
五、虚函数的访问
- 给p2发送的消息X仅限于f、g、k,因为p2的静态类型为A类,而在A类中无h函数。
- 若给p2发送消息f,则找到B类中的f,而B中f大括号内的g()和k()全部视为this->g()/k(),其中this->g()找到A类中,g()中的k()视作this->k(),也是A类中的k(),非虚函数为静态编联。而f中的this->k()在B类中,其中的h()视作this->h(),仍在B类中,非虚函数为静态编联。
示例:
-
p->vf()时,输出Derived::vf()。
-
p->vg()时,派生类中无vg函数,所以先输出
Base::vg()
,而this->vf()是虚函数,因此动态编联,输出Derived::vf()
,this->nvh()是非虚函数,静态编联,输出Base::nvh()
,this->vf()是虚函数,因此动态编联,输出Derived::vf()
-
p->nvh()时,由于静态类型Base中nvh()为非虚函数,所以先输出
Base::nvh()
,而this->vf()是虚函数,采用动态编联,输出Derived::vf()
-
delete p时,由于Base类的析构函为虚函数,所以动态编联执行派生类的析构函数,执行vf(),而派生类析构之后会再析构基类,所以会再执行基类析构函数中的vf().输出
Derived::vf() Base::vf()
因此本段程序的完整输出结果为:
六、具体类与抽象类
- 抽象类示例:图形的shape类(其子类可包括圆、矩形等),动物类(其子类可包括猫、狗等)
- 纯虚定义必须类外实现!
- 接口类中公有的成员函数功能要求能被子类全部实现,作为公共接口的功能
七、运行时类型识别
C++语言要想使用运行时类型识别,要求类中必须有虚函数
1、
- 一般通过虚机制实现,且效率较低。
- typeid不是函数,是操作符!
2、常见于父类向其子类转化:
动态类型转换