如果子类定义了与父类中原型相同的函数会发生什么——
函数重写
在子类中定义与父类中原型相同的函数
函数重写只发生在父类与子类之间
重载与重写区别:
重载:同一个作用域;
子类无法重载父类函数,父类同名函数将被覆盖;
重载是在编译期间根据参数类型和个数决定;
重写:发生于父类、子类之间;
父类和子类函数有相同的函数原型;
使用virtual关键字声明后能够产生多态;
运行期间根据具体对象类型决定调用的函数。
面向对象中的多态
C++中通过virtual关键字对多态进行支持
使用virtual声明的函数被重写后即可展现多态特性
多态成立的三个条件
1.继承 2.虚函数重写 3.父类指针指向子类对象
静态联编和动态联编
1、C++与C相同,是静态编译型语言
2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。
动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。
虚析构函数
虚析构函数:通过父类指针释放子类对象
在什么情况下应当声明虚函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
多态
原理
- 当类中声明虚函数时,编译器会在类中生成一个虚函数表
- 虚函数表是一个存储类成员函数指针的数据结构
- 虚函数表是由编译器自动生成与维护的
- virtual成员函数会被编译器放入虚函数表中
- 当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行函数调用时,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可。)
- VPTR一般作为类对象的第一个成员
说明1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
说明2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数
说明3 :
C++编译器,执行run函数,不需要区分是子类对象还是父类对象
构造函数中调用虚函数不能实现多态——构造的顺序是先构造父类再构造子类,当调用父类构造函数时,虚函数指针vfptr指向父类的虚函数表,而当调用子类构造函数时,虚函数指针 vfptr 指向子类的虚函数表
父类指针指向子类数组时,由于父类和子类的数据长度不同,导致父类指针的步长短于子类,所以如果用父类指针指向子类数组,访问时会出现内存错误,即段错误。
纯虚函数和抽象类
- 纯虚函数是一个在基类中说明的虚函数,在基类没有定义,要求任何派生类中都必须定义自己的版本。
- 纯虚函数为各派生类提供一个公共界面(接口)。
- 纯虚函数说明形式
virtual 类型 函数名(参数表) = 0
- 一个具有纯虚函数的基类成为抽象类
抽象类的概念:
1.含有纯虚函数的类
2.抽象类不能用于直接创建对象,但可以声明抽象类的指针或引用
3.可使用指向抽象类的指针支持运行时多态性
4.派生类中必须实现基类中的纯虚函数,否则它仍将被看作一个抽象类