多态(PolyMorphism)
如果有几个相似而不完全相同的对象,有时人们要求在向它们发出同一个消息时,它们的反应各不相同,分别执行不同的操作。这种情况就是多态现象。
例如,甲乙丙3个班都是高二年级,他们有基本相同的属性和行为,在同时听到上课铃声的时候,他们会分别走向三个不同的教室,而不会走向同一个教室。
又如在windows环境下,用鼠标双击一个对象(这就是向对象传递一个消息),如果对象是一个可执行文件,则会执行此程序,如果对象是一个文本文件,则会启动文本编辑器并打开该文件。
C++中所谓的多态(polymorphism)是指,由继承而产生相关的不同的类,其对象对同一消息会作出不同的相应。
多态性是面向对象程序设计的一个重要特征,能增强程序的灵活性。可以减轻系统升级,维护,调试的工作量和复杂度。
多态实现的前提:赋值兼容
赋值兼容的规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。赋值兼容是一种默认行为,不需要任何的显示的转化步骤。
赋值兼容规则中所指的替代包括以下的情况:
* 派生类的对象可以复制给基类对象
* 派生类的对象可以初始化基类的引用
* 派生类对象的地址可以赋值给指向基类的指针
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
class Shape
{
public:
Shape(int x, int y)
:_x(x), _y(y){}
void draw()
{
cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl;
}
protected:
int _x;
int _y;
};
Class Circle: public Shape
{
public:
Circle(int x, int y, int r)
:Shape(x, y), _radius(r){}
void draw()
{
cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl;
cout<<"radius = "<<_radius<<endl;
}
private:
int _radius;
};
// 赋值兼容只发生在公有派生的父子关系中
// 子类对象赋值给父类对象
// 子类对象赋值给父类的引用
// 子类对象的地址赋值给父类的指针
int main()
{
Shape s(1,2);
s.draw();
Circle c(3, 4, 5);
c.draw()
return 0;
s = c;
c.draw(); // 调用Shape中的draw函数
Shape & rc = c;
rc.draw();// 调用Shape中的draw函数
Shape * ps = &c;
ps->draw();
return 0;
}
多态形成的条件
静多态
前面学习的函数重载,也是一种多态现象,通过命名倾轧在编译阶段决定,故称为静多态。
动多态
动多态,不是在编译器阶段决定,而是在运行阶段决定,故称为动多态。动多态形成的条件如下:
1. 父类中有虚函数,声名虚函数的方法;
2. 子类override(覆写)父类中的虚函数
3. 通过已被子类对象赋值的父类指针或引用,调用共用接口
class Shape
{
public:
Shape(int x, int y)
:_x(x), _y(y){}
virtual void draw()
{
cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl;
}
protected:
int _x;
int _y;
};
class Rect: public Shape
{
public:
Rect(int x, int y, int len, int wid)
:Shape(x, y), _len(len), _wid(wid){}
virtual void draw()
{
cout<<"start from"<<"("<<_x<<", "<<_y<<")"<<endl;
cout<<"len = "<<_len<<"wid = "<<_wid<<endl;
}
private:
int _len;
int _wid;
};
Class Circle: public Shape
{
public:
Circle(int x, int y, int r)
:Shape(x, y), _radius(r){}
void draw()
{
cout<<"draw from "<<"("<<_x<<", "<<_y<<")"<<endl;
cout<<"radius = "<<_radius<<endl;
}
private:
int _radius;
};
int main()
{
Circle c(1, 2, 3);
Shape *ps = &c';
ps->draw(); // 表现的是Circle的特性
ps = new Rect(3, 4, 5, 6);
ps->draw(); // 表现的是Rect的特性
}
声明虚函数的方法,virtual void func();声明型关键字
overload:同一作用域中,函数名相同,参数列表不同
shadow: 发生在父子类中的同名成员
override: 发生在父子类中,父类中函数有virtual声明的函数,子类中同参同名同返回,函数之间构成覆写
覆写的函数,是不是一定同父类的访问方式一致,看子类的需求。
虚函数小结
- 在基类中用virtual声明成员函数为虚函数,类外实现虚函数时,不必再加virtual。
- 在派生类中重新定义此函数称为覆写,要求函数名、返值类型、函数参数个数及类型全部匹配,并根据派生类的需要重新定义函数体。
- 当一个成员函数被声明为虚函数后,其派生类中完全相同的函数(显示地写出)也为虚函数,可以在其前加virtual以示清晰。
- 定义一个指基类对象地指针,并使其指向其子类的对象,通过该指针调用虚函数,此时调用的就是指针变量指向对象的同名函数。
- 子类中的覆写的函数,可以为任意访问类型,依据子类需求决定。
纯虚函数
格式
class 类名
{
virtual 函数声明 = 0;
}
纯虚函数没有实现体,含有纯虚函数的类,称为抽象基类,不可以实例化。
Java interface 接口,作用就是给族类提供接口用的。
如果一个类中声明了纯虚函数,而在派生类中没有对该函数的定义,则该虚函数在派生类中仍然为纯虚函数,派生类仍然为纯虚基类。
虚析构
为了析构完全,在delete父类指针的时候,将子类对象析构完全。
class Animal
{
public:
Animal();
virtual ~Animal();
virtual void voice() = 0;
};
当一个类中有虚函数的时候,需要将其析构函数一并virtual。
含有虚函数的类,析构函数也应该声明为虚函数。在delete父类指针的时候,会调用子类的析构函数,实现完整析构。
(1) 只有类的成员函数才能声明为虚函数
虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数
(2) 静态成员函数不能是虚函数
静态成员函数不受对象的捆绑,只有类的信息;实现多态必须要有对象
(3) 内联函数不能是虚函数
(4) 构造函数不能是虚函数
构造时,对象的创建尚未完成。构造完成后,才能算一个名副其实的对象
(5) 析构函数可以是虚函数且通常声明为虚函数