第7章多态性与虚函数7.1多态性的概念7.2重载多态7.3虚函数多态7.4虚析构函数7.5纯虚函数与抽象类计算机科学与技术学院7.1多态性的概念多态性是指:具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。多态性(polymorphism)是面向对象程序设计的一个重要特征。利用多态性可以设计和实现一个易于扩展的系统。计算机科学与技术学院从系统实现的角度看,多态性分为两类:静态多态性:是通过函数的重载实现的(运算符重载实质上也是函数重载)。动态多态性:是在程序运行过程中才动态地确定操作所针对的对象。它又称运行时的多态性。动态多态性是通过虚函数(virtualfunction)实现的。问题:当一个基类被继承为不同的派生类时,各派生类可以使用与基类成员相同的成员名,如果在运行时用同一个成员名调用类对象的成员,会调用哪个对象的成员?也就是说,通过继承而产生了相关的不同的派生类,与基类成员同名的成员在不同的派生类中有不同的含义。也可以说,多态性是“一个接口,多种方法”。重载多态:回顾前面的内容,其实已经接触过两种类型的多态。(1)函数重载就是多态一种形式(2)运算符重载也是多态的一种形式(前述Complex对运算重载)函数重载属于静态多态——在编译的时候就能确定调用哪一个重载函数。7.2重载多态能否用同一个调用形式,既能调用派生类又能调用基类的同名函数???在程序中通过对象指针去调用不同派生层次中的同名函数,如,用同一个语句“ps-display();”可以调用不同派生层次中的display函数,只需在调用前给指针变量pt赋以不同的值(使之指向不同的类对象)即可。7.3虚函数计算机科学与技术学院7.3虚函数例题7_1:派生类中有两个display函数(继承一个、新增一个),,两个函数不属于重载,编译系统按照同名覆盖的原则决定调用的对象。程序中用std.display()调用的是派生类student中的成员函数area。如果想调用中的直接基类person的display函数,应当表示为:std1.person::display()。程序中对基类和派生类分别使用了ps-display(),具体执行那一个函数??发现:不管是ps指向基类对象还是派生类对象,其实调用的都是基类的display()函数。这不是我们想要的!!!计算机科学与技术学院7.3虚函数回顾:赋值兼容规则:一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:派生类的对象可以被赋值给基类对象。派生类的对象可以初始化基类的引用。指向基类的指针也可以指向派生类。通过基类对象名、指针只能使用从基类继承的成员希望:能够用同一种调用方式调用不同类中的同名函数-----用虚函数来解决这个问题计算机科学与技术学院对7_1例子修改将person类中的display函数声明为虚函数:virtualvoiddisplay();调试运行程序发现,达到我们期望的目标程序中基类和派生类的display就叫做虚函数。当把基类的某个成员函数声明为虚函数后,允许在派生类中重新定义、赋予其新功能,并且使用基类指针指向同一类族中的不同对象,从而调用其中的同名函数。7.3虚函数计算机科学与技术学院由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。虚函数的使用方法是:在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。7.3虚函数计算机科学与技术学院C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。7.3虚函数计算机科学与技术学院7.3虚函数静态关联与动态关联关联(Binding,也称绑定)程序自身彼此关联的过程,确定程序中的操作调用与执行该操作的代码间的关系。静态关联(StaticBindingorAarlyBinding)关联工作出现在编译阶段,用对象名或者类名来限定要调用的函数。动态绑定(DynamicBindingorLateBinding)关联工作在程序运行时执行,在程序运行时才确定将要调用的函数。计算机科学与技术学院7.3虚函数静态关联具体应用函数重载属静态关联。通过对象名调用的虚函数,在编译时即可确定其调用的虚函数属于哪一个类,其过程为静态关联。动态关联具体应用通过基类指针去调用虚函数(例如“pt-display()”)。编译系统在编译该行时是无法确定调用哪一个类对象的虚函数的。在运行阶段,基类指针变量先指向了某一个类对象,然后通过此指针变量调用该对象中的函数。此时调用哪一个对象的函数无疑是确定的,由于是在运行阶段把虚函数和类对象“绑定”在一起的,因此,此过程为动态关联使用虚函数时,有两点要注意:(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。(2)一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。7.3虚函数在什么情况下应当声明虚函数计算机科学与技术学院7.3虚函数在什么情况下应当声明虚函数根据什么考虑是否把一个成员函数声明为虚函数呢?主要考虑以下几点:(1)首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。(2)如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。(3)应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。(4)有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。(5)构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的绑定。说明:使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtualfunctiontable,简称vtable),它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的,因此,多态性是高效的。当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。7.4虚析构函数例子7_2基类中有非虚析构函数时的执行情况。为简化程序,只列出最必要的部分。#includeiostreamusingnamespacestd;classPoint//定义基类Point类{public:Point(){}//Point类构造函数~Point(){cout″executingPointdestructor″endl;}//Point类析构函数};classCircle:publicPoint//定义派生类Circle类{public:Circle(){}//Circle类构造函数~Circle(){cout″executingCircledestructor″endl;}//Circle类析构函数private:intradius;};计算机科学与技术学院intmain(){Point*p=newCircle;//用new开辟动态存储空间deletep;//用delete释放动态存储空间return0;}运行结果为executingPointdestructor说明:p是指向基类的指针变量,指向new开辟的动态存储空间,希望用detele释放p所指向的空间。但只执行了基类Point的析构函数,而没有执行派生类Circle的析构函数。将析构函数声明为虚函数:virtual~Point(){cout″executingPointdestructor″endl;}运行结果为:executingCircledestructorexecutingPointdestructor带有纯虚函数的类称为抽象类:class类名{virtual类型函数名(参数表)=0;//纯虚函数...}7.5纯虚函数与抽象类计算机科学与技术学院说明:纯虚函数纯虚函数没有函数体;最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”;这是一个声明语句,最后应有分号。纯虚函数只有函数的名字而不具备函数的功能,不能被调用。它只是通知编译系统:“在这里声明一个虚函数,留待派生类中定义”。在派生类中对此函数提供定义后,它才能具备函数的功能,可被调用。抽象类凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。计算机科学与技术学院抽象类作用抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。注意抽象类只能作为基类来使用。不能声明抽象类的对象。构造函数不能是虚函数,析构函数可以是虚函数。例12.4虚函数和抽象基类的应用。在本章例12.1介绍了以Point为基类的点—圆—圆柱体类的层次结构。现在要对它进行改写,在程序中使用虚函数和抽象基类。类的层次结构的顶层是抽象基类Shape(形状)。Point(点),Circle(圆),Cylinder(圆柱体)都是Shape类的直接派生类和间接派生类。程序如下:第(1)部分#includeiostreamusingnamespacestd;//声明抽象基类ShapeclassShape{public:virtualfloatarea()const{return0.0;}//虚函数virtualfloatvolume()const{return0.0;}//虚函数virtualvoidshapeName()const=0;//纯虚函数};计算机科学与技术学院第(2)部分classPoint:publicShape//Point是Shape的公用派生类{public:Point(float=0,float=0);voidsetPoint(float,float);floatgetX()const{returnx;}floatgetY()const{returny;}virtualvoidshapeName()const{cout″Point:″;}//对虚函数再定义friendostream&operator(ostream&,constPoint&);protected:floatx,y;};//定义Point类成员函数Point::Point(floata,floatb){x=a;y=b;}voidPoint::setPoint(f