多态:是指相同的对象收到不同的消息或者不同的对象收到相同的消息时会产生不同的动作
静态多态(早绑定):
class Rec
{
Rec();
recArae(int height);
recArae(int height,int width);
}
int main()
{
Rec rec;
rec.recArae(10);
rec.recArae(10,20);
}
这种情况就是系统会根据参数的不同来确定对象应该调用哪个函数,这就是静态多态(早绑定)。
不同的对象,下达相同的指令,做着不同的操作:多态
比如说计算面积(圆形和句型的公式就不同)
多态必须以封装与继承为基础
所以有子类和父类
通过父类指针指向子类对象。这时,如果让父类指针分别调用计算面积的函数。这时调用的都是父类的函数。为了使用子类的函数,实现动态多态。
使用父类指针指向子类对象时,如果一个集合里又有三角形又有矩形又有圆形,但是每个类里面都有一个单独的计算面积的成员函数,采用虚函数的方式,可以确保找到正确的计算面积的成员函数。
动态多态(晚绑定):例如父类father有个方法名字叫go(){“父类的go”};子类son有个方法也叫go(){“son的go”};子类daughter有个方法也叫go(){“daughter的go”};
father *p1=new son;
father *p2=new daughter;
然后当我们执行p1->go();p2->go();输出的结果都只是父类的go;
如果想实现输出"son的go"和"daughter的go",就需要在父类的go()方法得前面加上关键字virtual变成。
这样再输入p1->go();p2->go();就可以输出"son的go"和"daughter的go"
动态多态(晚绑定):让相同的消息应用于不同的场合。
给父类的成员函数加virtual让其成为虚函数,进而实现动态多态
class Base
{
public:virtual ~Base(){}
virtual void fun()
{
cout<<"Base::fun"<
}
};
class Derived:public Base
{
public:
virtual void fun()
{
cout<<"Derived::fun"<
}
};
main()
{
Base *pB=new Derived; //使用父类指针指向子类,
pB->fun();//fun()是虚函数。
}
多态中存在的问题:内存泄露
例如图中,多定义了一个指针(申请了内存),在多态中通过父类指针指向子类对象,并且通过父类指针去操作子类对象中的虚函数时,
当使用delete去销毁对象,并想借助父类的指针销毁子类对象会出现问题(只会执行父类的析构函数),执行不到Circle的析构函数
Shape *shape = new Circle(3,5,4.0);
shape->calcArea();
delete shape1; //只会执行父类的析构函数,执行不到Circle的析构函数
shape1 = NULL
如果是子类指针指向子类对象时,析构时就会析构父类对象和子类对象
当“delete”后面跟着的是父类的指针,那么只会运行父类的析构函数
当“delete”后面跟着的是子类的指针,那么只会运行子类的析构函数,再运行父类的析构函数。
由于以上两点,在执行父类析构函数中释放内存的时候,由于没有运行到子类析构函数,而造成内存泄露。
解决办法:在父类的析构函数中使用虚析构函数。
必须引入虚析构函数,同样的使用virtual修饰父类的析构函数(子类的析构函数可写可不写virtual关键字,系统会自动填上,推荐写上)
接口类:仅含有纯虚函数的类无数据成员,只有成员函数,成员函数都是纯虚函数
纯虚函数:没有函数体,并且=0
从虚函数表来看纯虚函数:抽象类:含有纯虚函数的类抽象类无法实例化对象抽象类的子类只有将抽象类中的所有纯虚函数都做了实现,才能实例化对象
在C++中多态的实现是通过虚函数表实现的当类中仅含有虚析构函数,不含其它虚函数时,也会产生虚函数表每个类只有一份虚函数表,所有该类的对象共用同一张虚函数表两张虚函数表中的函数指针可能指向同一个函数。
虚函数的实现原理:
定义一个父类shape,如果其中有虚函数,那么每次实例化对象的时候,都会产生一个虚函数表指针,这个指针指向的是虚函数表的首地址,再经过偏移等操作会找到相应的虚函数指针,该指针指向的是虚函数的入口地址,从而可以调用虚函数
当定义一个子类的时候,如果子类没有同名的虚函数,是直接继承父类的虚函数,那么实例化子类的时候,也会产生虚函数表指针,指向的是子类的虚函数表,但是在子类的虚函数表中,虚函数指针指向的仍然是父类的虚函数入口地址,和父类的虚函数入口地址是一样的。但如果子类中有同名的虚函数,那么子类的虚函数表中的指向虚函数首地址的指针和父类中该同名虚函数的首地址是不同的(自己重新定义的函数),如下三图所示
执行完子类的析构函数,就会执行父类的析构函数。当我们通过父类的指针来指向子类的对象,销毁父类指针时,会首先根据虚函数表指针找到子类的虚析构函数地址执行子类析构函数,当执行完子类的析构函数后会自动执行父类的析构函数,从而达到目的。
函数的隐藏:
如果父类与子类具有同名函数,子类会隐藏父类的同名函数(子类指针调用)
函数的覆盖:
父类中具有虚函数:子类继承父类且没有重新定义该虚函数,则在子类的虚函数表中有一个指针指向父类该函数的入口地址;若子类中重新定义了该虚函数,则子类的虚函数表中指针指向自己的函数的入口地址,会覆盖到原来父类的虚函数的地址。
1、成员函数加上virtual关键字,在Shape类实例化对象的时候,就含有一个虚函数表指针,虚函数表
2、定义了虚析构函数,在Shape类实例化对象的时候,就含有一个虚函数表指针,虚函数表
3、由于父类定义了虚析构函数,能够传递到Circle类(子类),在子类实例化对象的时候,产生虚函数表,虚函数表指针。对于子类对象而言,前四个基本内存单元是虚函数表的地址,接下来是数据成员的地址
virtual在函数中的使用限制:
1、普通函数不能是虚函数(必须是某个类的函数)
2、静态函数不能是虚函数
3、内联函数不能是虚函数(会忽略掉inline特性,变成虚函数)
4、构造函数不能是虚函数
异常就是程序在运行过程当中出现的错误
常见的异常:
数组下标越界
除数为0
内存不足
处理方式:
对有可能发生错误的地方做出预见性的安排
关键字
try.....catch
throw 抛出异常,被catch抓住
try:尝试,尝试运行正常逻辑
catch:捕获,当运行正常逻辑时,发生异常,则会捕获。
异常处理逻辑throw:抛出异常try和catch是一对多的关系
异常处理关键字try...catch...和throw就是将主逻辑放在try块里,异常处理逻辑放在catch里面。基本思想:主逻辑与异常处理分离。好处看上去整齐,非常容易理解。