1、单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,*,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,1,第,3,章多态性,本章学习重点掌握内容:,多态的概念和作用,多态的实现方法,常见运算符的重载,静态联编和动态联编,虚函数、纯虚函数和抽象基类的概念和用法,虚析构函数的概念和作用,虚析构函数的用法,2,第,3,章多态性,3.1,多态性的概念,3.2,运算符重载,3.3,联编和虚函数,3.4,纯虚函数和抽象类,3.5,综合应用实例,3,3.1,多态性的概念,多态性就是,一个事物多种形态,,就是同一符号或者名字在不同情况下具有不同解
2、释的现象。,多态性有两种表现形式:,一种是,不同的对象在收到相同的消息时,产生不同的动作;,另一种是,同一对象收到相同的消息却产生不同的函数调用。,4,多态的两种实现方式,两种表现形式分别叫做:,编译时多态和运行时多态。,编译时多态,也叫静态多态性,属于早期绑定,在编译时就实现了绑定,它是,静态联编,的;,实现 方式,重载,包括函数重载。操作符重载,运行时多态,也叫动态多态性,属于晚期绑定,在编译时还无法确定绑定对象,只有在运行时才能够实现绑定,它是,动态联编,的。实现方式:,虚函数,5,3.2.1,运算符重载概述,复数类,Complex,class Complex,public:,Compl
3、ex()real=image=0;,Complex(double r,double i),real=r,image=i;,void Print();,private:,double real,image;,;,void,Complex:Print,(),if(image,0),cout,real-imagei;,else,cout,real+imagei;,6,3.2.1,运算符重载概述,complex c1(2.0,3.0),c2(4.0,-2.0),c3;c3=c1+c2;,编写程序来实现“,+”,运算符,来作用于,complex,类的对象,,这就是,运算符的重载,。,运算符重载是对已有的
4、运算符赋予多重含义,使同一个运算符作用于不同类型的数据时,导致不同类型的行为。,7,3.2.1,运算符重载概述,(,1,),一般来说,不改变运算符原有含义,,只让它能针对新类型数据的实际需要,对原有运算符进行适当的改造。例如,重载“,+”,运算符后,它的功能还是进行加法运算。,(,2,),重载运算符时,不能改变运算符原有的优先级别,,也不能改变运算符需要的操作数的数目。,重载之后运算符的优先级和结合性都不会改变。,(,3,)不能创建新的运算符,,只能重载,c+,中已有的运算符,。,(,4,)有些运算符不能进行重载。如:“,.”,类成员运算符、“*”类指向运算符、“:”类作用域运算符、“?:”条
5、件运算符及“,sizeof,”,求字节数运算符。,8,3.2.2,运算符重载的实现,运算符重载的本质就是函数重载。,属于,静态多态性,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数,.,这个过程是在编译过程中完成的。,运算符重载形式有两种:,类的成员函数,、,友元函数,。,9,语法形式如下:,函数类型,operator,运算符(,形参表,),函数体;,重载,一元运算符没有参数,;重载二元运算符只有一个参数。,一般基于某个对象调用成员函数,这个对象是一个,隐含的操作数,,就是被调用的运算符函数的一个操作数,而且是第一操作数,
6、有了运算符重载以后,当程序中出现表达式:,oCCounter2=,oCCounter2,+,oCCounter1;,的时候编译程序将其解释为:,oCCounter2=oCCounter2.,operator+,(oCCounter1),成员函数的调用只能够通过成员函数所属的类的对象来调用,也就是说被重载的运算符的第一操作数的类型是确定的,是隐含的,不可改变。,运算符重载,为,类的成员函数,10,运算符重载为类的友元函数,语法形式如下:,friend,函数类型,operator,运算符(,形参表,),函数体;,不是成员函数,也没有,this,指针,,用友元函数重载二元运算符(双目运算符)时,要有
7、两个参数;重载一元运算符(单目运算符)时,要有一个参数。,11,成员函数与友元函数重载区别,当运算符重载为类的成员函数时,函数的参数个数比原来的参与运算的运算数个数要,少一个,(后缀,+,、,-,除外,).,当运算符重载为类的友元函数时,参数个数与原运算数的个数相同。,12,单目和双目运算符重载的重载,一般来讲,,单目运算符最好重载为成员函数,而双目运算符则最好重载为友元函数。,1,单目运算符重载,可重载为没有参数的成员函数或者带有一个参数的非成员函数(友元函数),,其参数必须是用户定义类型的对象或者是对该对象的引用,13,单目运算符重载,单目运算符,+,和,-,,重载时,由于有前缀和后缀的差
8、别,其参数个数遵照以下格式规定,:,operator+,(),;/,前缀运算符重载,operator+,(,int,),;,/,后缀运算符重载,operator-,(),;,/,前缀运算符重载,operator-,(,int,),;,/,后缀运算符重载,前缀和后缀重载的区分,.,14,双目运算符重载,可以重载为带一个参数的成员函数或者带两个参数的友元函数。,问题:,CCounter,operator+(,CCounter,ob);,“oCCounter2=oCCounter1+20;”/,正确,“,oCCounter2=20+oCCounter1;”/,错误,解决,使用友元重载,friend,
9、CCounter,operator+(,CCounter,ob,int,);,friend,CCounter,operator+(,int,CCounter,ob);,15,3.2.3,双目运算符重载,【,例,3.1】,定义一个复数类,重载“,+”,运算符为复数类的成员函数,使这个运算符能直接完成两个复数的加法运算,以及一个复数与一个实数的加法运算。,【,例,3.2】,重载“,+”,运算符为复数类的友元函数,使这个运算符能直接完成两个复数的加法运算,以及一个复数与一个实数的加法运算。,【,例,3.3】,日期类,date,中采用友元形式重载“,+”,运算符,实现日期加上一个天数,得到新日期。,【
10、例,3.6】,重载单目运算符“,+”,。,16,3.2.4,赋值运算符重载,在,C+,中有两种类型的赋值运算符:一类是“,+=”,和“,-=”,等先计算后赋值的运算符,另一类是“,=”,即直接赋值的运算符。,1,运算符“,+=”,和“,-=”,的重载,【,例,3.4】,实现复数类“,+=”,和“,-=”,的重载。,17,3.2.4,赋值运算符重载,正常情况下,系统会为每一个类,自动生成,一个默认的完成上述功能的赋值运算符。这个赋值运算符完成的是,“复制赋值”,的功能。,但这种“复制赋值”是一种“浅复制”,即它将对象数据成员,“逐位”(,bit,)进行复制赋值。,(拷贝构造函数),成员变量包含
11、指针时,是非常危险的,所以需要重载赋值运算。同样也需要重载拷贝构造函数。,想想结构(,struct,)的赋值,注意:,赋值运算,系统自动生成,其他操作则不自动生成,例,3.5,18,3.2.6 下标运算符重载,下标运算符“,”,通常用于在数组中标识数组元素的位置,下标运算符重载可以实现数组数据的赋值和取值。,下标运算符重载函数只能作为类的成员函数,不能作为类的友元函数,。,下标运算符“,”,函数重载的一般形式为:,函数类型,operator,(形参表);,其中形参表为该重载函数的参数列表。重载下标运算符只能且必须带一个参数,该参数给出下标的值。,注意下标操作符需要出现在赋值操作符的左右两边,为
12、了能在左边出现,返回值必须是引用。,以字符类为例说明。,19,3.2.7,关系运算符重载,关系运算符也可以被重载,例如定义一个日期类,date,,重载运算符“,=”,和“,”,,用于两个日期的等于和小于的比较运算。,20,3.2.8,类型转换运算符重载,类型转换函是一种特殊类型的类成员函数 它定义了一个由用户定,义的转换 以便把一个类对象转换成某种其他的类型,类型转换运算符重载函数的格式如下:,operator,类型名(),函数体,类型转换运算符重载函数没有返回类型,因为类型名就代表了它的返回类型,而且也没有任何参数。在调用过程中要带一个对象实参。,可以同时定义多个类型转换函数,即类转换成多种
13、类型,例子实现复数类转换成,double,型和字符类转换成字符指针的例子,21,3.3,联编和虚函数,3.3.1,静态联编和动态联编,面向对象的多态性从实现的角度来讲,可以分为静态多态性和动态多态性两种。,在源程序编译的时候就能确定具有多态性的语句调用哪个函数,称为静态联编。对于重载函数的调用,就是在编译的时候确定具体调用哪个函数,所以是,属于静态联编,。,编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编,或称动态束定,又叫晚期联编。,22,3.3.2,
14、虚函数,在,C+,中,,动态联编是通过虚函数,来实现的。,虚函数的本质是将派生类类型的指针赋给基类类型的指针,虚函数被调用时会自动判断调用对象的类型,从而做出相应的响应。,【,例,3.10】,#include,class,CPerson,public:,void,PrintInfo,(),cout,Personn;,;,2026/3/8 周日,23,class,CWorker,:public,CPerson,private:,int,kindofwork,;,public:,void,PrintInfo,(),cout,Workern;,;,class,CTeacher,:public,CPe
15、rson,private:,int,subject;,public:,void,PrintInfo,(),cout,PrintInfo,();,p=p-,PrintInfo,();,CPerson,p1-,PrintInfo,();,程序的执行结果为:,Person,Person,Person,分析实际上,无论引用基类对象还是派生类对象,函数内调用的都是基类的,PrintInfo,(),成员函数。,通过基类的引用去引用派生类对象,,只能看到派生类从基类中继承而来的部分,。,这是由于,C+,的,静态联编机制,造成的。它首先将指向基类的指针与基类成员函数,PrintInfo,()连接在一起。这样,
16、不管,p,指向哪个对象,,p-,PrintInfo,()调用的总是基类的成员函数,PrintInfo,()。,24,虚函数,C+,提供了虚函数(,virtual function,)机制解决上述问题,,对于上面的例子,把基类的成员函数定义为虚函数,分析运行结果,。,。,通过虚函数机制,实现了,动态联编,即如果是虚函数,在运行时根据指针或引用,实际指向的对象,,调用对象的函数,普通函数只能根据,指针或引用,的类型,。(上述例子改成函数形式,效果更明显),一旦基类的成员函数定义为虚函数,则其派生类的同名成员函数(原型一致)不管前面是否加关键字,virtual,,,同样具有虚特性,同样是虚函数,。,
17、25,几条重要规则,-1,、基类中把成员函数定义为虚函数后,,派生类和基类的对应成员函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。,原型不一致,即使加上了关键字,virtual,,也不会进行滞后联编。,、类的成员函数才能说明为虚函数。,、静态成员函数不能是虚函数,静态成员函数是不受某个对象的限制,它属于类。,26,几条重要规则,-2,、内联,(inline),函数不是虚函数,内联函数不能在运行时动态确定位置,它在编译时采用插入函数体的方式处理。,、构造函数不虚函数,构造对象的时候,对象还是一片未定型的空间,只有对象构造完成后,对象才是具体类的实例。,、析构函数可以是虚函数,而且,通
18、常将析构函数声明为虚函数,。,7.,运行 时多态,必须通过指针和引用来实现,27,28,虚函数与重载函数的比较,(,1,)重载函数要求函数有相同的,函数名,,并有不同的,参数序列,;而虚函数则要求这三项(,函数名、返回值类型和参数序列,)完全相同;,(,2,)重载函数是同一层次上的同名函数问题,而虚函数是不同层次上的同名函数问题。,(,3,)如果是不同层次上的同名函数(三同),且不是虚函数,则会产生同名覆盖,(,2,)重载函数可以是,成员函数或友员函数,,而虚函数只能是,成员函数,;,(,3,)重载函数的调用是以,所传递参数序列的差别,作为调用不同函数的依据;虚函数是根据,对象的不同,去调用,
19、不同类,的虚函数;,(,4,),虚函数在运行时表现出多态功能,,,C+,的精髓,;而重载函数则在编译时表现出多态性,。,29,关于虚函数有以下几点说明,当基类中把成员函数定义为虚函数后,,派生类和基类的对应成员函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。,基类中虚函数前的,virtual,不能省略,派生类中的虚函数的,virtual,关键字可以省略,运行时多态须通过基类,对象的引用,或,基类对象的指针,调用虚函数才能实现。,虚函数必须是类的成员函数,,不能是友员函数,,,不能是静态成员函数,。,构造函数不能定义为虚函数,一般将析构函数定义为虚函数。,30,3.3.4,虚析构函数,
20、不能声明虚构造函数,因为在构造函数执行时,对象还没有完全构造好,不能按虚函数方式进行调用。,可声明虚析构函数,如果用基类指针指向一个,new,生成的派生类对象,通过,delete,作用于基类指针删除派生类对象时,有以下两种情况:,(,1,)基类析构函数不为虚析构函数,会调用基类的析构函数,派生类的析构函数不会被调用,派生类对象中派生的那部分内存空间无法析构释放。,(,2,)如果基类析构函数为虚析构函数,释放基类指针的时候会调用基类和派生类中的所有析构函数,派生类对象中所有的内存空间都将被释放,包括继承基类的部分。,所以,C+,中的析构函数通常是虚析构函数,。,31,3.3.4,虚析构函数,【,
21、例,3.12】,虚析构函数的用法和作用示例。,#include,class Base1,public:,Base1(),cout,Base1()n;,;,class Derived1:public Base1,public:,Derived1(),cout,Derived1()n;,;,class Base2,public:,virtual Base2(),cout,Base2()n;,;,2026/3/8 周日,32,class Derived2:public Base2,public:,Derived2(),cout,Derived2()n;,;,void main(),Base1*,bp
22、new Derived1;,delete,bp,;,Base2*b2p=new Derived2;,delete b2p;,运行结果:,Base1(),Derived2(),Base2(),33,3.4,纯虚函数和抽象类,在许多情况下,在基类中不能给出有意义的虚函数定义,,可以在基类,person,中加一个,displaySalary,函数,并声明为虚函数:,virtual void,displaySalary,(,int,m,int,s),return 0;,virtual void,displaySalary,(,int,m,int,s)=0;,这就将,void,displaySala
23、ry,(,int,m,int,s),声明为一个纯虚函数,(pure virtual function),,把它的具体定义留给派生类来做。,纯虚函数,在C+中,对于那些在基类中不需要定义具体的行为的函数,可以定义为纯虚函数。,声明纯虚函数的一般形式是,class 类名,virtual 类型 函数名(参数表)=0;/纯虚函数,.,;,如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。,34,35,3.4.2,抽象类,如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(,abstract class,)。,抽象类中不仅包括纯虚函数,也可包括虚函数。
24、抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。,抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,,而不能用于直接创建对象实例,。,可使用指向抽象类的指针支持运行时多态性。,抽象类定义的一般形式是:,class,类名,public:,virtual,函数名,(,参数表,)=0;,其他函数的声明,;,.,;,【,例,3.13】,抽象基类示例。,2026/3/8 周日,36,【,例,3.13】,抽象基类示例。,#include,class Person,public:,virtual void,PrintInfo,(),cout,Personn;,vi
25、rtual void,DisplaySalary(int,m,double,s)=0;,;,class Worker:public Person,private:,int,kindofwork,;,public:,void,PrintInfo,(),cout,Workern;,void,DisplaySalary(int,m,double,s),cout,工人全年的工资是,:m*s,endl,;,;,2026/3/8 周日,37,class Teacher:public Person,private:,int,subject;,public:,void,PrintInfo,(),cout,Te
26、achern;,void,DisplaySalary(int,m,double,s),cout,教师全年的工资是,:m*s,endl,;,;,class Driver:public Person,private:,int,subject;,public:,void,DisplaySalary(int,m,double,s),cout,司机全年的工资,:m*s,PrintInfo,();,p-DisplaySalary(12,1800.35);,p=p-,PrintInfo,();,p-DisplaySalary(12,1300.45);,p=p-,PrintInfo,();,p-DisplaySalary(12,1700.78);,运行结果:,Worker,工人全年的工资是,:21604.2,Teacher,教师全年的工资是,:15605.4,Person,司机全年的工资是,:20409.4,






