C++多态概述
同一个命令,不同对象做出不同的反应就是多态。(在JAVA中比较明显的表现形式就是重写和重载)
什么是多态?指相同对象收到不同消息或不同对象收到相同消息时产生不同的动作。
- 静态多态(早绑定)
早绑定:程序在运行之前,在编译阶段,已经确定下来到底使用哪个函数了,可见很早的就已经将函数编译进去了,这种情况叫做早绑定。 - 动态多态(晚绑定)
对不同的对象下达相同的指令,但是却做了不同的操作,动态多态的前提是必须以封装和继承为基础。就是要使用虚函数来做。
//定义一个Shape类
class Shape
{
public:
double calcArea()
{
cout<<"calcArea"<<endl;
return 0;
}
};
//圆形类
class Circle:public Shape
{
public:
Circle(double r);
double calcArea();
private:
double m_dR;
};
//实现圆类的实现面积的函数
double Circle::calcArea()
{
return 3.14*m_dR*m_dR;
}
//方形类
class Rect:public Shape
{
public:
Rect(double width,double height);
double calcArea();
private:
double m_dWidth;
double m_dHeight;
};
//实现方形类的计算面积的函数
double Rect::calcArea()
{
return m_dWidth*m_dHeight;
}
//主函数
int main(void)
{
Shape *shape1 = new Circle(4.0);
Shape *shape2 = new Rect(3.0,5.0);
shape1->calcArea();
shape2->calcArea();
.......
/*输出的结果是调用了Shape类下的
calcarea方法(都是父类的),而我们希
望的结果是分别调用了各自类下的
calcArea方法,因此要考虑用到虚函数实
现多态,来解决这个问题
*/
return 0;
}
虚函数
- 用virtual关键字修饰成员函数,使其成为一个虚函数。
- 在定义父类的时候就把我们想要实现多态的函数加virtual关键字,实现多态。
- 在子类的该函数的定义时,也加上virtual关键字,不过该处是可加可不加,在父类部分的virtual关键字是必须要加。
//同上的例子
class Shape
{
public:
virtual double calcArea() //虚函数
{
cout<<"calcArea"<<endl;
return 0;
}
};
虚析构函数
- 当存在继承关系时,使用父类的指针去指向堆中的子类的对象,并且想使用父类的指针去释放内存,这个时候需要虚析构函数。
- 只释放了父类对象,没能释放子类对象,意味着动态多态存在内存泄漏。
- virtual关键字去修饰析构函数,就叫虚析构函数。(父类必须写,子类可写可不写,最好写virtual)
- 通过虚析构函数解决在动态多态中存在的内存泄漏问题。
virtual在函数中的使用限制:
- 普通函数不能是虚函数,意味着这个函数必须是某个类的成员函数,不能是全局函数。
- 静态成员函数不能是虚函数。静态成员函数不属于任何一个对象,它是和这个类同生共死的。
- 内联函数不能是虚函数。如果去修饰内联函数,计算机会直接忽略inline关键字,而只把它当做虚函数。
- 构造函数不能是虚函数。
函数的覆盖和隐藏
隐藏:如果定义了父类和子类,父类和子类出现了同名函数,那么这个时候就称为函数的隐藏。
函数的覆盖:在子类中定义了同名的虚函数,在子类的虚函数表当中会把原来父类的虚函数的函数地址覆盖成子类的虚函数的函数地址,这就叫函数的覆盖。
加了virtual关键字之后,在Shape实例化对象的时候,实例化出来的对象当中就会含有一个虚函数表指针。
纯虚函数和抽象类
- 一个纯虚函数的例子:
class Shape
{
public:
//虚函数
virtual double calcArea()
{return 0;}
//纯虚函数
virtual double calcPerimeter()=0;
}
- 抽象类:
包含纯虚函数的类叫做抽象类。
抽象类无法实例化对象。
抽象类的子类也可以是抽象类。 - 接口类:
如果在抽象类中,仅含有纯虚函数而不含有其他任何函数和数据成员,就称为接口类。
运行时类型识别(RTTI)
typeid 和 dynamic_cast
//举个例子
class Flyable
{
public:
virtual void takeoff()=0; //起飞
virtual void land()=0; //降落
};
//定义一个鸟类
class Bird:public Flyable
{
public:
void foraging(){....}
virtual void takeoff(){....}
virtual void land(){....}
private:
.....
};
//定义一个飞机类
class Plane:public Flyable
{
public:
void carry(){....}
virtual void takeoff(){....}
virtual void land(){....}
};
//使用时,假设有如下的函数
void doSomething(Flyable *obj)
{
obj->takeoff();
如果是Bird,则觅食
如果是Plane,则运输
obj->land();
}
//如下写法
void doSomething(Flyable *obj)
{
obj->takeoff();
cout<<typeid(*obj).name()<<endl;
if(typeid(*obj)==typeid(Bird))
{
Bird *bird = dynamic_cast<Bird *>(obj);
bird->foraging();
}
obj->land();
}
- dynamic_cast注意事项:
- 只能应用于指针和引用的转换
- 要转换的类型中必须包含虚函数
- 转换成功返回子类的地址,失败返回NULL
- typeid的注意事项:
(1) type_id返回一个type_info对象的引用
(2)如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数
(3)只能够获取对象的实际类型
异常处理
- 异常:程序运行期出现的错误
- 异常处理:对有可能发生异常的地方做出预见性的安排