• 类的多态性的概念


    多态性(polymorphism)是面向对象程序设计的一个重要特征。利用多态性可以设计和实现一个易于扩展的系统。

    在C++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。

    在C++程序设计中,在不同的类中定义了其响应消息的方法,那么使用这些类时,不必考虑它们是什么类型,只要发布消息即可。从系统实现的角度看,多态性分为两类:静态多态性和动态多态性。

    前边学过的函数重载和运算符重载实现的多态性属于静态多态性,在程序编译时系统就能决定调用的是哪个函数,因此静态多态性又称编译时的多态性。 静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)。

    动态多态性是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。 动态多态性是通过虚函数(virtual function)实现的。

    有关静态多态性的应用已经介绍过了,这里主要介绍动态多态性和虚函数。

    要研究的问题是:当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个对象的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以说,多态性是“一个接口,多种方法”。
    下面是一个承上启下的例子。一方面它是有关继承和运算符重载内容的综合应用的例子,通过这个例子可以进一步融会贯通前面所学的内容,另一方面又是作为讨论多态性的一个基础用例。

    例12.1 先建立一个Point(点)类,包含数据成员x,y(坐标点)。以它为基类,派生出一个Circle(圆)类,增加数据成员r(半径),再以Circle类为直接基类,派生出一个Cylinder(圆柱体)类,再增加数据成员h(高)。要求编写程序,重载运算符“<<”和“>>”,使之能用于输出以上类对象。

    对于一个比较大的程序,应当分成若干步骤进行。先声明基类,再声明派生类,逐级进行,分步调试。声明基类Point类可写出声明基类Point的部分如下:
    #include <iostream>
    //声明类Point

    class Point
    {
       public:
       Point(float x=0,float y=0);//有默认参数的构造函数
       void setPoint(float ,float);//设置坐标值
       float getX( )const {return x;}//读x坐标
       float getY( )const {return y;}//读y坐标
       friend ostream & operator <<(ostream &,const Point &);//重载运算符“<<”
       protected ://受保护成员
       float x, y;
    };
    //下面定义Point类的成员函数
    Point::Point(float a,float b) //Point的构造函数
    {x=a;y=b;} //对x,y初始化
    void Point::setPoint(float a,float b) //设置x和y的坐标值
    {x=a;y=b;} //为x,y赋新值
    ostream & operator <<(ostream &output, const Point &p){output<<″[″<<p.x<<″,″<<p.y<<″]″<<endl; return output;}//重载运算符“<<”,使之能输出点的坐标

    以上完成了基类Point类的声明。

    现在要对上面写的基类声明进行调试,检查它是否有错,为此要写出main函数。实际上它是一个测试程序。
    int main( )
    {
       Point p(3.5,6.4);//建立Point类对象p
       cout<<″x=″<<p.getX( )<<″,y=″<<p.getY( )<<endl;//输出p的坐标值
       p.setPoint(8.5,6.8);//重新设置p的坐标值
       cout<<″p(new):″<<p<<endl;//用重载运算符“<<”输出p点坐标
    }
    程序编译通过,运行结果为
    x=3.5,y=6.4
    p(new):[8.5,6.8]
    测试程序检查了基类中各函数的功能,以及运算符重载的作用,证明程序是正确的。

    (2)声明派生类Circle在上面的基础上,再写出声明派生类Circle的部分:
    class Circle:public Point//circle是Point类的公用派生类
    {
       public:
       Circle(float x=0,float y=0,float r=0);//构造函数
       void setRadius(float );//设置半径值
       float getRadius( )const;//读取半径值
       float area ( )const;//计算圆面积
       friend ostream &operator <<(ostream &,const Circle &);//重载运算符“<<”
       private:
       float radius;
    };
    //定义构造函数,对圆心坐标和半径初始化
    Circle::Circle(float a,float b,float r):Point(a,b),radius(r){}
    //设置半径值
    void Circle::setRadius(float r){radius=r;}
    //读取半径值
    float Circle::getRadius( )const {return radius;}
    //计算圆面积
    float Circle::area( )const
    {
       return 3.14159*radius*radius;
    }
    //重载运算符“<<”,使之按规定的形式输出圆的信息
    ostream &operator <<(ostream &output,const Circle &c)
    {
       output<<″Center=[″<<c.x<<″,″<<c.y<<″],r=″<<c.radius<<″,area=″<<c.area( )<<endl;
       return output;
    }
    为了测试以上Circle类的定义,可以写出下面的主函数:
    int main( )
    {
       Circle c(3.5,6.4,5.2);//建立Circle类对象c,并给定圆心坐标和半径
       cout<<″original circle:\nx=″<<c.getX()<<″, y=″<<c.getY()<<″, r=″<<c.getRadius( )<<″, area=″<<c.area( )<<endl;//输出圆心坐标、半径和面积
       c.setRadius(7.5);//设置半径值
       c.setPoint(5,5);//设置圆心坐标值x,y
       cout<<″new circle:\n"<<c;//用重载运算符“<<”输出圆对象的信息
       Point &pRef=c;//pRef是Point类的引用变量,被c初始化
       cout<<″pRef:″<<pRef;//输出pRef的信息
       return 0;
    }
    程序编译通过,运行结果为
    original circle:(输出原来的圆的数据)
    x=3.5, y=6.4, r=5.2, area=84.9486
    new circle:(输出修改后的圆的数据)
    Center=[5,5], r=7.5, area=176.714
    pRef:[5,5] (输出圆的圆心“点”的数据)

    (3)声明Circle的派生类Cylinder前面已从基类Point派生出Circle类,现在再从Circle派生出Cylinder类。
    class Cylinder:public Circle// Cylinder是Circle的公用派生类
    {
       public :
       Cylinder (float x=0,float y=0,float r=0,float h=0);//构造函数
       void setHeight(float );//设置圆柱高
       float getHeight( )const;//读取圆柱高
       loat area( )const;//计算圆表面积
       float volume( )const;//计算圆柱体积
       friend ostream& operator <<(ostream&,const Cylinder&);//重载运算符<<”
       protected :
       float height;//圆柱高
    };
    //定义构造函数
    Cylinder::Cylinder(float a,float b,float r,float h):Circle(a,b,r),height(h){}
    //设置圆柱高
    void Cylinder::setHeight(float h){height=h;}
    //读取圆柱高
    float Cylinder::getHeight( )const {return height;}
    //计算圆表面积
    float Cylinder::area( )const { return 2*Circle::area( )+2*3.14159*radius*height;}
    //计算圆柱体积
    float Cylinder::volume()const {return Circle::area()*height;}
    ostream &operator <<(ostream &output,const Cylinder& cy)
    {
       output<<″Center=[″<<cy.x<<″,″<<cy.y<<″],r=″<<cy.radius<<″,h=″<<cy.height <<″\narea=″<<cy.area( )<<″, volume=″<<cy.volume( )<<endl;
       return output;
    } //重载运算符“<<”
    可以写出下面的主函数:
    int main( )
    {
       Cylinder cy1(3.5,6.4,5.2,10);//定义Cylinder类对象cy1
       cout<<″\noriginal cylinder:\nx=″<<cy1.getX( )<<″, y=″<<cy1.getY( )<<″, r=″
          <<cy1.getRadius( )<<″, h=″<<cy1.getHeight( )<<″\narea=″<<cy1.area()
          <<″,volume=″<<cy1.volume()<<endl;//用系统定义的运算符“<<”输出cy1的数据
       cy1.setHeight(15);//设置圆柱高
       cy1.setRadius(7.5);//设置圆半径
       cy1.setPoint(5,5);//设置圆心坐标值x,y
       cout<<″\nnew cylinder:\n″<<cy1;//用重载运算符“<<”输出cy1的数据
       Point &pRef=cy1;//pRef是Point类对象的引用变量
       cout<<″\npRef as a Point:″<<pRef;//pRef作为一个“点”输出
       Circle &cRef=cy1;//cRef是Circle类对象的引用变量
       cout<<″\ncRef as a Circle:″<<cRef;//cRef作为一个“圆”输出
       return 0;
    }
    运行结果如下:
    original cylinder:(输出cy1的初始值)
    x=3.5, y=6.4, r=5.2, h=10 (圆心坐标x,y。半径r,高h)
    area=496.623, volume=849.486 (圆柱表面积area和体积volume)
    new cylinder: (输出cy1的新值)
    Center=[5,5], r=7.5, h=15 (以[5,5]形式输出圆心坐标)
    area=1060.29, volume=2650.72(圆柱表面积area和体积volume)
    pRef as a Point:[5,5] (pRef作为一个“点”输出)
    cRef as a Circle:Center=[5,5], r=7.5, area=176.714(cRef作为一个“圆”输出)

    在本例中存在静态多态性,这是运算符重载引起的。可以看到,在编译时编译系统即可以判定应调用哪个重载运算符函数。

    在类的继承层次结构中,在不同的层次中可以出现名字相同、参数个数和类型都相同而功能不同的函数。编译系统按照同名覆盖的原则决定调用的对象。

    在例12.1程序中用cy1.area( )调用的是派生类Cylinder中的成员函数area。如果想调用cy1中的直接基类Circle的area函数,应当表示为:cy1.Circle::area( )。用这种方法来区分两个同名的函数。

    但是这样做很不方便。人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数。在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们。

    例如,用同一个语句“pt->display( );”可以调用不同派生层次中的display函数,只需在调用前给指针变量pt赋以不同的值(使之指向不同的类对象)即可。C++中的虚函数就是用来解决这个问题的。

    虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。

    请分析例12.2。这个例子开始时没有使用虚函数,然后再讨论使用虚函数的情况。

    例12.2 基类与派生类中有同名函数。在下面的程序中Student是基类,Graduate是派生类,它们都有display这个同名的函数。
    #include <iostream>
    #include <string>
    using namespace std;
    //声明基类Student
    class Student
    {
       public:Student(int, string,float);//声明构造函数void display( );//声明输出函数
       protected ://受保护成员,派生类可以访问
       int num;
       string name;
       float score;
    };
    //Student类成员函数的实现
    Student::Student(int n, string nam,float s)//定义构造函数{num=n;name=nam;score=s;}
    void Student::display( )//定义输出函数{cout<<″num:″<<num<<″\nname:″<<name<<″\nscore:″<<
     


  • 相关阅读:
    调用网易有道词典api
    函数设计
    参数2
    新浪微博API使用初步介绍——解决回调地址的问题
    参数关键点和return返回多个(伪多个)值问题
    函数基本理论
    一个值得思考的例子
    Beego基础学习(五)Golang原生sql操作Mysql数据库增删改查(基于Beego下测试)
    Golang利用select实现超时机制
    Golang利用select和普通函数分别实现斐波那契数列
  • 原文地址:https://www.cnblogs.com/riskyer/p/3285768.html
Copyright © 2020-2023  润新知