C++中所谓的多态是指由继承产生相关的不同的类,其对象会对同一消息做出不同的反应
比如使用绘图软件时选择不同的图形,执行同一个托动作就可以绘制不同的图形
多态是面向对象的一个重要特征,能增加程序的灵活性,可以减轻系统的维护工作量
多态的基本要求:赋值兼容
赋值兼容规则是指,在需要基类的任何地方,都可以使用公有派生类来进行替代。
只有在公有派生类中才有赋值兼容,赋值兼容是一种默认行为,不需要任何的显示的转化步骤
一般有三种赋值兼容:
1.派生类赋给基类
2.派生类赋给基类的引用
3.派生类地址赋给基类的指针,多态中最常用的就是这个
class A{
public:
......
}
class B:public A{
public:
......
{
int main(){
A a;
B b;
a = b; //派生类赋给基类
A &ra = b; //派生类赋给基类的引用
A *pa = &b; //派生类地址赋给基类的指针,多态中最常用的就是这个
}
其中1,2不常用,多态中最常用的是派生类地址赋给基类的指针
多态的达成条件
多态一般有三个条件:
1.父类中有虚函数,即公共接口
2.子类重写(override)父类中的虚函数
3.通过已被子类对象赋值的父类指针,调用相关的操作
格式:
class base{
virtual type function()...
}
前面提到过拖动画出不同的图形,这就是典型的多态,我们可以简单地来实现一下:
#include <iostream>
using namespace std;
//多态实例
class shape {
public:
shape(int x, int y) :_x(x), _y(y) {}
virtual void draw(); //定义虚函数
protected:
int _x;
int _y;
};
void shape::draw()
{
cout << "draw shape from: " << _x << "," << _y << endl;
}
class circle :public shape {
public:
circle(int x, int y, int r) :shape(x, y), _radius(r) {}
void draw() override; //虚函数重写,格式必须同名同参同返回
protected:
int _radius;
};
void circle::draw()
{
cout << "draw shape from: " << _x << "," << _y << "," << _radius << endl;
cout << "shape" << this << endl;
cout << typeid(this).name() << endl;
}
class Rect : public shape {
public:
Rect(int x,int y,int w,int h):shape(x,y){
_width = w;
_height = h;
}
void draw() override; //虚函数重写
protected:
int _width;
int _height;
};
void Rect::draw()
{
cout << "draw shape from: " << _x << "," << _y << "," << _width <<","<<_height<<endl;
cout << "shape" << this << endl;
cout << typeid(this).name() << endl;
}
int main() {
circle cr(3, 4, 5);
cr.draw();
cout << endl << endl;
shape* PFcr = &cr;
PFcr->draw(); //一个接口,呈现出不同的行为,称为多态
Rect rc(7, 8, 9, 10);
PFcr = &rc;
PFcr->draw();
system("PAUSE");
}
纯虚函数
纯虚函数,是一类没有实现体,切被初始化为0的函数
1纯虚函数只有声明,没有实现,被初始化为"0"
2含有纯虚函数的类,被称为抽象基类,不可被实例化,即不能创建对象,存在的意义就是被继承,提供公共接口
3如果一个类中声明了纯虚函数,而派生类中没有该函数的定义,则该虚函数在派生类中仍为虚函数,派生类仍未虚基类
4虚基类是无法实例化的(即无法创建对象)
class virbase{
virtual type function = 0;
}
我们可以修改刚刚的图形继承案例,将draw函数改成纯虚函数,shape类就成了抽象基类,子类则需要对其进行重写操作
补充:虚函数的继承
多重继承中,孙子类可以覆写子孙类中的接口,比如下面这个例子中,子类没有重写父类的抽象函数,而是由孙子类进行了重写
这是允许的
class A {
public:
virtual void func() = 0;
};
class B :public A {
public:
};
class C :public B {
public:
void func() override; //孙子类重写祖先类虚函数
};
void C::func()
{
cout << "C::" << endl;
}
int main() {
C c;
A* pa = &c;
pa->func();
system("PAUSE");
}