C++多态全方面详解。

目录

一、什么是多态

二、静态连编

三、动态连编

四、代码示例

五、多态的具体作用



一、什么是多态

顾名思义多态就是多种形态的意思。

例如:去游乐场玩,儿童买票是半价,成年人买票是全价,老年人买票打七折。 对比类的话,这个买票就是一个类的一个方法(函数)。儿童,成年人,老年人即是对象,这些对象调用同一个买票函数得出来的不同的票价,这就是多态。

我们教材里面对多态性的描述是:不同对象收到相同的的消息时(即调用相同的函数),产生不同的动作。直观的说,多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的方式来调用这些具有不同功能的同名函数。


在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的血量减一

你可能感兴趣的:(C++,c++,开发语言)