目录
一、什么是多态
二、静态连编
三、动态连编
四、代码示例
五、多态的具体作用
顾名思义多态就是多种形态的意思。
例如:去游乐场玩,儿童买票是半价,成年人买票是全价,老年人买票打七折。 对比类的话,这个买票就是一个类的一个方法(函数)。儿童,成年人,老年人即是对象,这些对象调用同一个买票函数得出来的不同的票价,这就是多态。
我们教材里面对多态性的描述是:不同对象收到相同的的消息时(即调用相同的函数),产生不同的动作。直观的说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的方式来调用这些具有不同功能的同名函数。
在C++中,多态的实现和连编这一概念有关。所谓连编就是把函数名与函数体的程序代码连接在一起的过程。连编又分为静态连编和动态连编。(这里可以先看后面静态连编和动态连编的介绍)。
从实现的角度来说,多态可划分成两类:编译时的多态和运行时的多态。
编译时的多态是通过函数重载和运算符重载(重载采用的是静态连编)实现。(注意:函数重载实现的多态是一个类的行为的多态,要在同一个类内才能体现出来的多态,是面向方法的多态)。
运行时多态主要是通过虚函数和函数重写(虚函数采用的是动态连编)来实现的。(注意:虚函数可以实现继承的多态也可以实现非继承的多态,是面向对象的多态)。
预备知识:
1, 赋值兼容
在基类和公有派生类之间存在赋值兼容原则,也就是说所有需要基类类型的地方,都可以
用派生类去替代!
具体:
可以用派生类对象初始化基类对象
可以用派生类对象初始化基类引用
可以用派生类对象给基类对象赋值
可以基类指针指向派生类对象
公有派生类对象 是一个 基类对象!
2,函数隐藏与函数重写
在派生类中可以重新定义基类的某个函数
(1) 如果这个重新定义的函数是一般的成员函数,则称为 函数隐藏。
函数隐藏只需要派生类函数名与基类函数名相同即可,不需要函数类型和参数类型相同。
当用派生类对象去调用这个重新定义的函数时,不会再去调用基类继承来的那个函数(自动
被隐藏),而是会自动调用派生类自己实现的版本。
如果确实需要在派生类中去调用这个被隐藏的函数,必须加上作用域!
如: 基类名::函数名();
(2) 如果这个重新定义的函数是虚函数,则称为 函数重写,不仅需要函数名相同,而且函数类型
和参数列表都要相同。在c++11中新增了一个上下文关键字 override来显式说明这种情况。
3, 虚函数
(1) 用关键字virtual说的函数,称为虚函数。
(2) 如果基类中的某些函数被说明为虚函数,那么也意味着 允许它的派生类去重新定义这个函数。
(3) 如果基类的函数被声明为虚函数,那么在派生类中所有与这个函数原型相同的那些函数,自动成为虚函数!如果基类的某一个虚函数不希望被派生类覆盖,可以使用final来加以修饰。
(4) 当虚函数配合指针或引用使用时,能够实现多态效果!
多态: 具有继承关系的多种类型,称为多态类型。在C++中,所谓的多态,指在继承这层关系中, 给不同的对象 发送 相同的 消息,可以产生不同的行为。(给对象发消息,就是调用对象的方法)。
静态连编时,系统用实参与形参进行匹配,对于同名的重载函数变根据参数上的差异进行区分,然后进行连编,从而实现编译时的多态。(注意:函数的选择是基于指针类型或者引用类型)
动态连编是运行阶段完成的连编。即当程序调用到某一函数的时候,系统会根据当前的对象类型去寻找和连接其程序的代码,对面向对象的程序而言,就是当对象收到某一消息的时候,才去寻找和连接相应的方法。(函数的选择是基于基类指针指向的对象的类型)。
1.当没用使用虚函数时
#include
using namespace std;
class A{
public:
void show()
{
cout<<"我是A"<show();
pc=&b; //赋值兼容规则
pc->show();
}
运行结果:
我是A
我是A
这里有同学就会问了:pc指针后面不是指向了b对象吗?不应该是调用b对象的show函数吗?最后一句不应该是输出“我是B”吗?
都是输出“我是A”的原因是这些函数采用的都是静态连编,而静态连编选择函数是基于指向对象的指针类型,这里pc的类型是A,所以一直都是调用a对象的show函数。
那么怎么改才能实现不同的输出呢? 答案就是使用虚函数。
2.使用虚函数
#include
using namespace std;
class A{
public:
virtual void show()
{
cout<<"我是A"<show();
pc=&b; //赋值兼容规则
pc->show();
}
运行结果:
我是A
我是B
只需要在父类的show函数前面加一个virtual关键字就行了,加了之后show函数就会采用动态编译,只有到调用到它的时候,才去根据调用它的对象类型去匹配相应的函数体。
在以继承方式实现运行时多态的时候,子类的同名函数必须修改其函数体并且在函数的前面最好加上一个virtual关键字,因为通过继承的方式实现多态的时候,系统通过会根据一些规则自动识别出子类的虚函数。(ps:不通过继承关系也可以实现运行时多态,这时两个不同类的函数名都需要加上virtual才行,而且定义的指针指向其他对象时,要进行强制转换,下面举个例子给大家看一下。)
#include
#include
using namespace std;
class person{
public:
virtual int test()
{
string h="人";
cout<test();
dog d;
pr=(person*)&d; //两个完全不想关的类之间不能通过赋值兼容规则赋值了,要强制转换。
pr->test();
}
运行结果:
人
狗
。
在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即程序需要修改或增加功能时,只需改动或增加较少的代码。此外,使用多态也能起到精简代码的作用。
下面给大家列举一个设计游戏时用到多态的简单例子:
#include
using namespace std;
//将每个英雄的共同特征抽象出来形成一个抽象类
class hero{
public:
virtual void blood()=0;
};
//A英雄继承了抽象类实现了抽象类里面的方法
class A:public hero{
public:
void blood()
{
cout<<"A的血量减一"<blood();
}
/* 如果不通过继承实现多态的话,后期每增加一个英雄,防御都要增加一个对应的掉血函数。
void beat(A *p)
{
p->blood();
}
void beat(B *p)
{
p->blood();
}
*/
};
int main()
{
A a,*pc;
B b;
tower t;
t.beat(&a);//防御塔攻击a
t.beat(&b);//防御塔攻击b
}
运行结果:
A的血量减一
B的血量减一