多态公有继承与虚函数
①多态公有继承
作用;
① 子类public的部分中拥有父类的public属性和public方法
② 子类的private部分拥有父类的private属性和private方法,,但只能通过父类的public和protected方法访问
初始化:通过调用父类的构造器和子类的构造器完成
指针和引用的特殊用法:
可以指向子类或者父类对象,
父类指针和引用可以指向子类的对象,
子类指针和引用不可以指向父类
②虚方法
(Ⅰ)virtual修饰方法,
如果方法被声明为virtual,那么系统将根据指针,引用指向的对象的类型(也就是实际类型)来选择方法,
如果方法被没有virtual,那么根据指针,引用类型选择调用的方法,也就是根据编译类型
图示说明:
如果希望子类重写父类方法,则可以在父类方法声明前面加上virtual
虚方法的一些语法:
① 父类virtual方法在子类中自动加上virtual修饰,也就是说在派生类中自动成为虚方法
通常应该将父类析构函数声明为virtual的,这是一个好习惯!!
(Ⅱ)virtual修饰析构函数
为何需要virtual析构函数
将析构函数声明为虚的可以根据实际类型来销毁对象,而不是根据指针类型
这确保了对象将会被正确析构,内存资源正常释放
(Ⅲ)静态联编和动态联编
函数名联编
将源代码中函数调用解释为特定的代码块执行,就是函数名联编
静态联编
c++中函数调用是根据函数名称和形参列表来选出最佳候选函数,
因此,在编译过程中可以确定的函数调用就是静态联编,也叫早期联编
对象调用和非virtual方法属于静态联编
动态联编
由于有子类方法和父类方法,要根据对象的类型来选择方法,
编译器并不知道运行时产生的对象的类型,只知道指针和引用的类型
所以这个函数调用不能在编译时期确定下来
要等到运行时才能确定,这就是动态联编,也就是晚期联编
编译器对虚方法采用动态联编
动态联编和静态联编的区别与联系:
效率方面
编译器会采取一些办法来追踪父类指针或者引用指向的对象类型
因此,这增加了额外的开销
因此:静态联编比动态联编效率高!!,默认的函数调用是静态联编!!
概念模型方面
如果不希望子类重新定义该方法,就不要将该方法声明为virtual
仅将那些希望被子类重新定义的函数声明为virtual
子类如何访问父类方法,变量?
使用作用域解析运算符 :: 指出父类方法变量即可
举个例子:
Base::fun();
Base::variable;
用父类名称+ :: +具体方法/变量就可以了
虚函数表的工作原理
通常,编译器处理虚函数的方法是:
为每个对象定义一个隐藏成员,这个隐藏成员指向了一个存储虚函数地址的指针数组的首地址
这种数组就是虚函数表
虚函数表的一些基本操作
①:父类有一个隐藏成员指针,这个指针指向了父类所有虚函数地址表,
派生类对象将包含另外一个独立的地址表的指针。
因此父类和子类的虚函数表的首地址是不同的,指向的是两个不同的内存空间
②
如果子类中重新定义了该virtual方法,那么该方法在子类的虚函数表中就会有另外的新地址,
覆盖父类的那个virtual方法的地址,
如果子类还是用的父类的virtual方法,将在子类虚函数表中保存原始版本的地址
③子类增加的新的virtual函数,那么该函数地址也将被添加到虚函数表中
调用虚函数
调用虚函数时,将会在虚函数表里面遍历指针数组,不断的寻址,找到后就去
特定的地址里面执行函数代码块
虚函数表的额外开销
内存和指向速度都有一定成本;
①对象的内存空间增大,增大量为存储地址的空间,就是指针数组的大小和隐藏的成员指针
②对于每个类,都将创建一个指针数组
③对于每次虚函数的调用,都要不断的寻址,执行了额外的操作
虚函数的注意事项
①原则:
仅将在子类中重新定义的函数声明为virtual
②构造函数
构造函数不能声明为virtual,子类不能继承父类的构造器,因此virtual修饰构造器没有意义
③析构函数
应该将析构函数声明为virtual,除非该类不派生子类,
有子类的话,声明了virtual析构就是为了保证对象能够被正确的销毁,并且正确的释放内存
④友元函数
不可以把友元函数声明为virtual,因为virtual不是类成员
所以继承的时候子类不会获得友元函数,
这就好比是儿子继承父亲财产的时候,不能够继承父亲的人缘关系
⑤方法的覆盖
如果子类重新定义了方法,那么子类中新定义从方法将覆盖父类中所有的同名方法,
编译器不管你形参列表怎么样,只要你方法名字和父类的相同了,
就是直接覆盖父类中所有的同名方法,在用代码调用方法时时不可以使用父类的方法的
并不可以说我子类的重新定义的方法形参列表和父类不同,那我应该是函数重载啊,
但是实际上就是只有子类中的方法可以用了,
如果不想繁琐的重写所有父类的方法,使用using语句导入父类方法即可!!
私有继承public父类方法时,该using可以用父类的方法
如果是private方法,using父类方法也访问不了了
举例说明:
#include<iostream>
using namespace std;
class Object
{
private :
int height;
public ://假若注释public,那么using也不管用了
Object(int h_) : height(h_){}
double getHeight(double i) {return i;}
int getHeight()
{
return height;
}
};
class SubClass : private Object//私有继承
{
public :
using Object::getHeight;//使用父类的方法,导入父类同名的所有方法
int getHeight(long a)
{
return (int)a;
}
SubClass(int h_) : Object(h_){}
};
int main()
{
SubClass obj(188);
cout << obj.getHeight(8.0);
cout << obj.getHeight(999L);
}
运行效果
(Ⅰ)返回型类型协变
重新定义virtual方法后如果返回的是一个父类的指针或者引用,
则【可以】把返回值类型声明为指向子类的指针或者引用,
因为允许返回值类型随类类型的变化而变化