当前位置: 首页 > news >正文

第十一章 多态

        多态是面向对象开发过程中一个非常重要的概念。

11.1 多态概述

11.1.1 什么是多态

        多态(polymorphism),从字面理解是“多种形态,多种形式”,是一种将不同的特殊行为泛化为当个特殊记号的机制。

多态从实现的角度可划分为两类:

(1)编译时的多态:编译过程中确定了同名操作的具体操作对象

(2)运行时的多态:程序运行时才动态确定操作所针对的具体对象

11.1.2  多态的引入

先尝试理解以下程序:

#include<iostream>
using namespace std;class Animal
{public:void sleep(){cout<<"Animal sleep"<<endl;}void breathe(){cout<<"Animal breath"<<endl;}
};
class Fish:public Animal
{public:void breathe(){cout<<"Fish double"<<endl;}
};
int main()
{Fish fh;Animal *an=&fh;an->breathe();return 0;
}

运行结果:

分析过程如下:

如何解决这个问题呢?可以通过多态来解决。

11.1.3 联编

联编是确定操作的具体对象的过程,只计算机程序自身彼此关联的过程,即把一个标识符名和一个存储地址联系在一起的过程

根据进行阶段的不同,可以分为静态联编与动态联编,这两种联编过程分别对应多态的两种实现方式,如下图:

11.2 函数重载

        函数重载与运算符重载可以实现编译时的多态性。以下只介绍函数重载,运算符重载见第12章。

        函数重载也称为多态函数

(1)作用:使程序能够用同一个名字来访问一组相关的函数,提高程序灵活性。

(2)含义:函数名相同,但函数所带的参数个数或数据类型不同(编译系统会根据参数来决定调用哪个同名函数)        

        函数重载表现为两种情况:

(1)参数个数或类型有所差别的重载

(2)函数的参数完全相同但属于不同类

第一种情况与构造函数重载类似,不作追诉,只介绍第二种情况。当函数的参数完全相同但属于不同类时,为了让编译能正确区分调用哪个类的同名函数,可用以下两种方法别:

(1)用对象名区别,在函数名前加上对象名来限制对象名.函数名

(2)用类名和作用域运算符区别,在函数名前加“类名::”来限制(类名::函数名

示例代码:

#include<iostream>
using namespace std;class Point
{int x,y;public:Point(int a,int b){x=a;y=b;}double area(){return x*y;}};
class Circle:public Point
{int r;public:Circle(int a,int b,int c):Point(a,b){r=c;}double area(){return 3.1416*r*r;}
};
int main()
{Point pob(15,15);Circle cob(20,20,10);cout<<"pob.area()="<<pob.area()<<endl<<endl;cout<<"cob.area()"<<cob.area()<<endl<<endl;cout<<"cob.Point::area()"<<cob.Point::area()<<endl<<endl;return 0;
}

运行结果:

通过函数重载方式,可以是同名函数实现不同功能,即实现了静态多态性

11.3 虚函数

         虚函数是实现运行时多态的一个重要方式,是重载的另一种形式。也就是动态编联。

11.3.1 定义虚函数

        虚函数的定义是在基类中进行的,即把基类中需要定义为虚函数的成员函数声明为virtual。虚函数定义的一般形式如下:

virtual <函数类型><函数名>(参数表)
{函数体;
}
virtual void breathe()
{cout<<"Animal breathe"<<endl;
}

        当基类中的某个成员函数被声明为虚函数后,就可以在派生类中重新定义。在派生类中重新定义时,其函数原型包括返回类型、函数名、参数个数和类型,参数的顺序都必须与基类中的原型完全一致

示例代码:(与第一个代码做对比)

#include<iostream>
using namespace std;class Animal
{public:void sleep(){cout<<"Animal sleep"<<endl;}virtual void breathe(){cout<<"Animal breath"<<endl;}
};
class Fish:public Animal
{public:void breathe(){cout<<"Fish double"<<endl;}
};
int main()
{Fish fh;Animal *an=&fh;an->breathe();return 0;
}

运行结果:

代码将基类中的成员函数breathe()定义为虚函数,即加上virtual关键字,然后在主函数main()中定义Animal对象指针指向Fish的对象fh,调用breathe()函数后,得到的结果就是预期输出Fish bubble的结果。具体分析过程如下:

在使用派生类对象指针时应该注意:

(1)不能指向私有派生类的对象;

(2)指向公有派生类对象时,只能访问派生类中从基类继承下来的成员,不能直接访问公有派生类中定义的成员。

(3)指向派生类对象的指针不能指向基类的对象;

虚函数可以很好的实现多态,在使用虚函数是应注意的问题如下:

(1)虚函数的声明只能出现在类函数原型的声明中,不能出现在函数体实现时;

(2)基类中只有保护成员或公有成员才能被声明为虚函数

(3)在派生类中重新定义虚函数时,关键字virtual可以写也可以不写,但在容易引起混乱时,应该写上关键字;

(4)动态编联只能通过成员函数来调用或通过指针、引用来访问虚函数

在派生类中程序定义基类中的虚函数,是函数重载的另一种形式,但它与函数重载又有区别:

(1)一般函数重载,要求其函数的参数数量或参数类型必须有所不同,函数的返回值也可以不同;

(2)重载一个虚函数时,要求函数名、返回类型、参数个数、参数的类型和参数的顺序必须与基类中的虚函数的原型完全相同

11.3.2 多级继承和虚函数

        多级继承可以看作是多个单继承的组合,多级继承的虚函数与单继承的虚函数的调用相同。即不同类创建的对象调用的函数是不一样的。

示例代码:

#include<iostream>
using namespace std;class Base
{public:virtual void func(){cout<<"Base output"<<endl;}};
class Derived1:public Base
{public:void func(){cout<<"Derived1 output"<<endl;}
};
class Derived2:public Derived1
{public:void func(){cout<<"Derived2 output"<<endl;}
};
void test(Base &b)
{b.func();
}int main()
{Base bObj;Derived1 d1Obj;Derived2 d2Obj;test(bObj);cout<<endl;test(d1Obj);cout<<endl;test(d2Obj);cout<<endl;return 0;
}

运行结果:

        上述代码中定义了一个多级继承,在基类中定义了虚函数func(),在主函数main()中调用该函数时,不同类创建的对象调用的函数是不一样的,即实现了多态的“一个接口,多种实现”。

11.4 纯虚函数与抽象类

        抽象类是一种包含纯虚函数的特殊类。建立抽象类时为了多态地使用抽象类的成员函数。

11.4.1 纯虚函数

        在基类中不能为虚函数给出一个有意义的实现时,可以将其声明为纯虚函数。纯虚函数的实现可以留给派生类来完成。纯虚函数的作用是为派生类提供一个一致的接口。一般来说,一个抽象类带有至少一个纯虚函数。纯虚函数的一般定义如下:

        纯虚函数与普通函数定义的不同在于书写形式加了“=0”,说明在基类中不用定义改函数的函数体。

示例代码:纯虚函数的函数体由派生类定义;

#include<iostream>
using namespace std;class Point
{protected:int x0,y0;public:Point(int i=0,int j=0){x0=i;y0=j;                }    virtual void set()=0;//声明纯虚函数
};
class Line:public Point
{protected:int x1,y1;public:Line(int i=0,int j=0,int m=0,int n=0):Point(i,j){x1=m;y1=j;}void set()//定义接口函数{cout<<"Line::set() called \n";}
};void setobj(Point *p)
{p->set();
}int main()
{Line *lineobj = new Line;setobj(lineobj);return 0;
}

运行结果:

11.4.2 抽象类

        抽象类是包含纯虚函数的一种特殊类,是为了抽象和设计而建立的,处于继承层次结果的教上层。抽象类是不能建立对象的,为了强调一个类是抽象类,可讲该类的构造函数声明为保护的访问控制权限。抽象类的主要作用如下:

(1)将有关的类组织在一个继承层层次结构中,由抽象类来为它们提供一个公共的根,相关的子类是从这个根派生出来的。

(2)抽象类只描述这组子类沟通的操作接口,而完整的实现留给子类

使用抽象类是应注意:

(1)抽象类只能用做其他类的基类,不能创建抽象类的对象

(2)抽象类不能用做参数类型、函数的返回类型或显示转换的类型

(3)可以声明抽象类的对象指针或对象引用,从而可以访问派生类对象成员,实现多态编联

(4)若派生类中没有给出抽象类的所有纯虚函数的函数体,派生类仍然是一个抽象类,若派生类中给出抽象类的所有纯虚函数的函数体,则这个派生类不再是抽象类,可以创建自己的对象。

示例代码:

#include<iostream>
using namespace std;class Vehicle
{protected:float speed;int total;public:Vehicle(float speed,int total){Vehicle::speed=speed;Vehicle::total=total;}virtual void ShowMember()=0;
};
class Car:public Vehicle
{protected:int aird;public:Car(int aird,float speed,int total):Vehicle(speed,total){Car::aird=aird;}void ShowMember(){cout<<"the speed is:"<<speed<<endl;cout<<"the total is:"<<total<<endl;cout<<"the aird is:"<<aird<<endl;}
};int main()
{//Vehicle a(100,4);Car b(250,150,4);b.ShowMember();return 0;
}

运行结果:

声明抽象类Vehicle的对象a(100,4)时报错,不能为抽象类声明对象。

相关文章:

  • Linux下编译并打包MNN项目迁移至其他设备
  • RTMP 协议解析 1
  • 摸鱼屏保神器工具软件下载及使用教程
  • AIGC在游戏开发中的革命:自动化生成3A级游戏内容
  • Vue3 组件通信与插槽
  • Qt开发:如何加载样式文件
  • 玩转Pygame绘图:从简单图形到炫酷精灵
  • 【开源飞控】调试
  • maven打包时配置多环境参数
  • 设置右键打开VSCode
  • MCP协议:AI与数据世界的“万能连接器“
  • [创业之路-390]:人力资源 - 社会性生命系统的解构与重构:人的角色嬗变与组织进化论
  • SpringBoot技术概述与应用实践
  • GPT系列模型-20250426
  • 《软件设计师》复习笔记(6.1)——信息安全及技术
  • 常见的机器视觉通用软件
  • 数据安全和合规性市场分析
  • Redis常见面试题——List对象
  • Redis 数据分片三大方案深度解析与 Java 实战
  • Python爬虫实战:获取高考资源网各学科精品复习资料
  • 四川落马厅官周海琦受审,1000余人接受警示教育
  • 多地征集农村假冒伪劣食品违法线索,全链条整治“三无”产品
  • 商务部:将积极会同相关部门加快推进离境退税政策的落实落地
  • 上海举行金融服务企业“走出去”推进大会
  • 联手华为猛攻主流市场,上汽集团总裁:上汽不做生态孤岛
  • 首开股份:去年亏损约81.4亿元,是公司发展史上极其困难的一年