C++ 多态
1. 多态的意义
如果有几个相似而不完全相同的对象,有时人们要求在向它们发出同一个消息时,它们的反应各不相同,分别执行不同的操作。这种情况就是多态现象。
C++中所谓的多态(polymorphism)是指,由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。
多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。可以减轻系统升级,维护,调试的工作量和复杂度。
2. 多态实现的前提
2.1 规则
赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。赋值兼容是一种默认行为,不需要任何的显示的转化步骤。赋值兼容规则中所指的替代包括以下的情况:
- 派生类的对象可以赋值给基类对象。
- 派生类的对象可以初始化基类的引用。
- 派生类对象的地址可以赋给指向基类的指针。
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
#include <iostream>
using namespace std;
class Shape
{
public:
Shape(int x,int y)
:_x(x),_y(y){}
void draw()
{
cout<<"draw Shap ";
cout<<"start ("<<_x<<","<<_y<<") "<<endl;
}
//private:
protected:
int _x;
int _y;
};
class Circle:public Shape
{
public:
Circle(int x, int y,int r)
:Shape(x,y),_r(r){}
void draw()
{
cout<<"draw Circle ";
cout<<"start ("<<_x<<","<<_y<<") ";
cout<<"radio r = "<<_r<<endl;
}
private:
int _r;
};
int main()
{
Shape s(3,5);
s.draw();
Circle c(1,2,4);
c.draw();
s = c;
s.draw();
Shape &rs = c;
rs.draw();
Shape *ps = &c;
ps->draw();
return 0;
}
2.2 补充
父类也可以通过强转的方式转化为子类。 父类对象强转为子类对象后,访问从父类继承下来的部分是可以的,但访问子类的部分,则会发生越界的风险,越界的结果是未知的。
#include <iostream>
using namespace std;
class Shape
{
public:
Shape(int x,int y)
:_x(x),_y(y){}
void draw()
{
cout<<"draw Shap ";
cout<<"start ("<<_x<<","<<_y<<") "<<endl;
}
//private:
protected:
int _x;
int _y;
};
class Circle:public Shape
{
public:
Circle(int x, int y,int r)
:Shape(x,y),_r(r){}
void draw()
{
cout<<"draw Circle ";
cout<<"start ("<<_x<<","<<_y<<") ";
cout<<"radio r = "<<_r<<endl;
}
private:
int _r;
};
int main()
{
Shape s(3,5);
s.draw();
Circle c(1,2,4);
c.draw();
s = c;
s.draw();
Shape &rs = c;
rs.draw();
Shape *ps = &c;
ps->draw();
//c = dynamic_cast<Circle>(s); //缺少转化函数
//c.draw();
Circle * pc = static_cast<Circle*>(&s);
pc->draw();
return 0;
}
3. 多态实现的条件
3.1 多态
3.1.1 静多态
前面学习的函数重载,也是一种多态现象,通过命名倾轧在编译阶段决定,故称为静多态。
3.1.2 动多态
动多态,不是在编译器阶段决定,而是在运行阶段决定,故称为动多态。动多态形成的条件如下:
1,父类中有虚函数。
2,子类继承父类且 override(覆写)父类中的虚函数。
3,通过被子类对象赋值的父类指针或引用,调用共用接口。
3.2 虚函数
3.2.1 格式
class 类名
{
virtual 函数声明;
}
3.2.2 例子
Shape类
virtual void draw()
{
cout<<"draw Shap ";
cout<<"start ("<<_x<<","<<_y<<") "<<endl;
}
Circle类
void draw()
{
cout<<"draw Circle ";
cout<<"start ("<<_x<<","<<_y<<") ";
cout<<"radio r = "<<_r<<endl;
}
Rect类
void draw()
{
cout<<"draw Rect";
cout<<"start ("<<_x<<","<<_y<<") ";
cout<<"len = "<<_len<<" wid = "<<_wid<<endl;
}
main.cpp
int main()
{
Circle c(1,2,4);
c.draw();
Rect r(2,3,4,5);
r.draw();
Shape *ps;
int choice;
while(1) //真正的实现了动多态,在运行阶段决定。
{
scanf("%d",&choice);
switch(choice)
{
case 1:ps = &c;ps->draw();break;
case 2:ps = &r;ps->draw();break;
}
}
return 0;
}
3.2.3 小结
1,在基类中用 virual 声明成员函数为虚函数。类外实现虚函数时,不必再加 virtual。
2,在派生类中重新定义此函数称为覆写,要求函数名,返值类型,函数参数个数及类型全部匹配。并根据派生类的需要重新定义函数体。
3,当一个成员函数被声明为虚函数后,其派生类中完全相同的函数(显示的写出)也为虚函数。 可以在其前加 virtual 以示清晰。
4,定义一个指基类对象的指针,并使其指向其子类的对象,通过该指针调用虚函数,此时调用的就是指针变量指向对象的同名函数。
5,子类中的覆写的函数,可以为任意访问类型,依子类需求决定。
3.3 纯虚函数
3.3.1 格式
class 类名
{
virtual 函数声明 = 0;
}
3.3.2 例举
Shape类
virtual void draw() = 0;
Circle类
void draw()
{
cout<<"draw Circle ";
cout<<"start ("<<_x<<","<<_y<<") ";
cout<<"radio r = "<<_r<<endl;
}
3.3.3 小结
1.含有纯虚函数的类,称为抽象基类,不可实列化。即不能创建对象,存在的意义就是被继承,提供族类的公共接口,java 中称为 interface。
2.纯虚函数只有声明,没有实现,被“初始化”为 0。
3.如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类。
4.含有虚函数的类,析构函数也应该声明为虚函数。在 delete 父类指针的时候,会调用子类的析构函数,实现完整析构。
4. 若干限制
1)只有类的成员函数才能声明为虚函数
虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数。
2)静态成员函数不能是虚函数
静态成员函数不受对象的捆绑,只有类的信息。
3)内联函数不能是虚函数
4)构造函数不能是虚函数
构造时,对象的创建尚未完成。构造完成后,才能算一个名符其实的对象。
5)析构函数可以是虚函数且通常声明为虚函数。