您好,欢迎访问三七文档
当前位置:首页 > 电子/通信 > 综合/其它 > 面向对象程序设计之多态性与虚函数.
Object-OrientedProgramminginC++第六章多态性与虚函数C++6-16.1多态性的概念多态性(polymorphism)是面向对象程序设计的重要特征。一个算法语言如果只支持类,而不支持多态,只能说是基于对象的语言,如Ada,VB。C++支持多态性,在C++程序设计中能够实现多态性。利用多态性,可以设计和扩展一个易于扩展的系统。什么叫多态?多态的意思是一种事物的多种形态。在C++中,是指具有不同功能的函数可以用同一个函数名。面向对象方法中一般是这样描述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法)。C++6-2写出程序运行结果#includeiostream#includestringUsingnamespacestd;classstudent{public:student(intn,stringnam,floats){num=n;name=nam;score=s;}voiddisplay(){cout“num:”num“name:”name“score:”scoreendl;}protected:intnum;stringname;floatscore;};classgraduate:publicstudent{public:graduate(intn,stringnam,floats,floatp):student(n,nam,s),pay(p){}voiddisplay(){cout“num:”num“name:”name“score:”score“pay:”payendl;}private:floatpay;};voidmain(){students1(1001,”Li”,98.5);graduateg1(2001,”Liu”,90.5,800.5);student*pt=&s1;pt-display();pt=&g1;pt-display();}C++6-36.1多态性的概念我们其实已经接触过多态性的现象。如函数的重载多态性分类:从系统实现的角度看,多态性分为以下两类:静态多态性:又称编译时的多态性。如函数重载属于静态多态性。动态多态性:有称为运行时的多态性。它主要表现为虚函数(virtualfunction)。C++6-46.3虚函数能否用一个调用形式,既能调用派生类的函数,又能调用基类同名函数?C++中的虚函数就是用来解决这一问题。虚函数的作用:虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。C++6-56.3虚函数#includeiostream.h#includestring.hclassstudent{public:student(intn,stringnam,floats){num=n;name=nam;score=s;}voiddisplay(){cout“num:”num“name:”name“score:”scoreendl;}protected:intnum;stringname;floatscore;};classgraduate:publicstudent{public:graduate(intn,stringnam,floats,floatp):student(n,nam,s),pay(p){}voiddisplay(){cout“num:”num“name:”name“score:”score“pay:”payendl;}private:floatpay;};voidmain(){students1(1001,”Li”,98.5);graduateg1(2001,”Liu”,90.5,800.5);student*pt=&s1;pt-display();//指向基类对象s1pt=&g1;pt-display();//指向派生类对象g1,仅输出了派生类的基类数据成员,因为它调用的是基类成员函数display!}假如想输出派生类的全部数据,当然可以采用下面两种方法之一:通过派生类对象名g1,调用派生类对象的成员函数:g1.display();定义一个指向派生类的指针ptr,并指向g1,然后用ptr-display()。C++6-66.3虚函数我们可以用虚函数可以顺利解决这一问题。方法是:在基类student中声明display函数时,在最左边加上一个关键字virtual:virtualvoiddisplay();就可以student类的display函数声明为虚函数,程序其它部分不变编译运行后,可见,使用pt-display(),的确将graduate类对象g1的全部数据显示了出来,说明它调用的是g1的成员函数display。在派生类中重新定义该函数,要求函数名、函数类型、参数表完全相同。但不加virtual关键字。只在类里的成员函数声明时,加上关键字virtual,在类外定义定义虚函数时,不加virtual关键字。定义一个指向基类对象的指针,并使她指向同一类中的某一对象;通过该指针标量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数,而不是基类的同名函数!C++6-76.3虚函数通过使用虚函数和指针,就能方便地调用同一类族中不同类对象的同名函数,只要先用基类指针指向该对象即可。#includeiostream.h#includestring.hclassstudent{public:student(intn,stringnam,floats){num=n;name=nam;score=s;}virtualvoiddisplay(){cout“num:”num“name:”name“score:”scoreendl;}protected:intnum;stringname;floatscore;};classgraduate:publicstudent{public:graduate(intn,stringnam,floats,floatp):student(n,nam,s),pay(p){}voiddisplay(){cout“num:”num“name:”name“score:”score“pay:”payendl;}private:floatpay;};voidmain(){students1(1001,”Li”,98.5);graduateg1(2001,”Liu”,90.5,800.5);student*pt=&s1;pt-display();//指向基类对象s1pt=&g1;pt-display();//指向派生类对象g1,调用g1的显示函数display,打印出g1全部数据成员}C++6-86.3虚函数将函数重载与虚函数比较,可见:函数重载是解决的是同一层次上的同名函数的问题。是横向重载。虚函数解决的是不同派生层次上的同名函数的问题。相当于纵向重载。同一类族的虚函数的首部是相同的;而重载函数的首部不相同(参数表不能相同)。C++6-96.3虚函数静态关联与动态关联C++在编译或运行时,对多个同名函数究竟调用哪一个函数,需要一定的机制来确定。这种确定调用的具体对象的过程称为“关联(binding)”,即把函数名与某一个类对象捆绑在一起。函数重载,在编译时就可确定其调用的函数是哪一个;通过对象名调用虚函数,在编译时也可确定其调用的虚函数属于哪一个类。其过程称为“静态关联(staticbinding),因为是在运行前进行关联的,又成为早期关联(earlybinding)。C++6-106.3虚函数通过指针和虚函数的结合,在编译阶段是没法进行关联的,因为编译只作静态的语法检查,光从语句形式pt-display()无法确定调用的对象,也就没法关联。出现这种情况,我们可以在运行阶段来处理关联。在运行阶段,基类指针先指向某一个对象,然后通过指针调用该对象的成员函数。此时调用哪个函数是确定的,没有不确定因素。例如语句pt=&g1;pt-display();非常确定的是调用g1对象的成员函数display。这种情况由于是在运行阶段将虚函数与某一类对象“绑定”在一起的,因此称为“动态关联(dynamicbinding),或“滞后关联(latebinding)”。C++6-116.3虚函数使用虚函数,要注意只能用virtual声明类的成员函数,类外的普通函数不能声明成虚函数,因为它没有继承的操作。一个成员函数被声明成虚函数后,在同一类族中的类就不能再定义一个非virtual的、但与该函数具有相同参数表和返回类型的同名函数。使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtualfunctiontable,vtable),它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的,所以多态性运行效率非常高。C++6-126.3虚函数什么情况下使用虚函数?成员函数所在的类是否会作为基类?成员函数被继承后有没有可能发生功能变化,如果两个因素都具备,就应该将它声明成虚函数。如果成员函数被继承后功能不变,或派生类用不到该函数,就不要声明成虚函数。应考虑对成员函数的访问是通过对象名还是基类指针,如果是通过基类指针或引用访问,则应当声明为虚函数。有时基类中定义虚函数时并不定义它的函数体,即函数体为空。其作用只是定义了一个虚函数名,具体功能留给派生类添加(6.4节会讨论这种情况)。C++6-136.3虚函数虚析构函数问题的引出:我们知道,当派生类对象撤消时,系统先调用派生类析构函数,再调用基类析构函数。但是,如果用new运算符建立了一个派生类临时对象,但用一个基类指针指向它,当程序用带指针参数的delete撤消对象时,会发生让人不能接受的情况:系统只析构基类对象,而不析构派生类对象:classpoint{public:point(){}~point(){cout“析构基类对象”endl;}};classcircle:publicpoint{public:circle(){}~circle(){cout“析构派生类对象”endl;}private:intradius;};intmain(){point*p=newcircle;//指针为指向基类对象指针,//但实际指向临时派生类对象deletep;return0;}C++6-146.3虚函数实际上,程序只析构了基类对象,而没有析构派生类对象,为什么呢?因为指针p为基类指针,系统认为它只有基类对象,而与派生类对象无关。实际上,由于该指针被指向了一个临时派生类对象,所以还应该这个临时的析构派生类对象。解决的办法:可以将基类的析构函数声明为虚析构函数。如:virtual~point(){cout“析构基类对象”endl;}程序其它部分不动,就行了。当基类的析构函数被定义成virtual,无论指针指向同一类族中的哪一个对象,当撤消对象时,系统会采用动态关联,调用相应的析构函数,清理该对象,然后再析构基类对象。C++6-156.4纯虚函数与抽象类前面已经提到,有时在基类中将某一成员函数定为虚函数并不是基类本身的需要,而是派生类的需要。在基类中预留一个函数名,具体功能留给派生类根据需要去定义。在上一节中基类point中有定义面积area函数,是因为“点”没有面积的概念。但是,其直接派生类circle和间接派生类cylinder却都需要area函数,而且这两个area函数的功能还不相同,一个是求圆面积,一个是求圆柱体表面积。也许会想到,在基类point中加一个area函数,并声明为虚函数:virtualfloatarea()const{return0
本文标题:面向对象程序设计之多态性与虚函数.
链接地址:https://www.777doc.com/doc-1960762 .html