资源描述
*,千里之行,始于足下,单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,*,汇天地,之,长,育,学子精英,C+,面向对象程序设计教程(,下,),第,章,多态性,Chapter 6,Polymorphism,第 章 模板,Chapter 7,Template,3,6,7,所谓多态性就是不同对象收到相同的消息时,产生不同的动作。通俗的说,多态性是指用,一个名字定义不同的函数,,这些函数执行不同但又有类似的操作,即用同样的接口访问功能不同的函数,从而实现“一个接口,多种方法”,。,class point,int,x,y;,public:point(,int,a,int,b),x=a;y=b;,float area()return 0.0;,;,class circle:public point,int,radius;,public:circle(,int,x,int,y,int rad,):point(x,y),radius=,rad,;,float area()return 3.141*radius*radius;,;,void main(),point p(20,20);,circle c(8,8,30);,cout,p.area(),endl,;,cout,c.area(),endl,;,cout,c.point:area(),endl,;,5.2 函数重载,在基类和派生类中的函数重载有两种情况,:,(1)参数有差别,(2)函数所带的参数完全相同,只是他们属于不同的类。,通过(1)对象的不同加以区分(2)通过“类名:”加以区分。,5.3 运算符重载,int,x,y,z;,z=x+y;,这是将两个整数相加的方法,非常简单.若有一个复数类,complex,class complex,public:,double real;,double,imag,;,complex(double r=0,double i=0)real=r;,imag,=i;,;,把,complex,的两个对象,com1,和,com2,加在一起。能否这样,实现:,complex com1(1.1,2.2),com2(3.3,4.4),total1;,total1=com1+com2;,C+,为运算符提供了一种方法,即在进行运算符重载时,必须写一个运算符函数,其名字为,operator,后随一个要重载的运算符。,函数,功能,operator+(),加法,operator-(),减法,operator*(),乘法,operator/(),除法,operator(),小于,表5.1运算符函数,这样,编译器在一个运算符的两边,“,看,”,到自定义的数据类型,就执行型用户自己的函数,而不是内部运算符的常规程序.,class complex,public:double real;,double,imag,;,complex(double r=0,double i=0)real=r;,imag,=i;,;,complex operator+(complex co1,complex co2),complex temp;,temp.real=co1.real+co2.real;,temp.,imag,=co1.,imag,+co2.,imag,;,return temp;,void main(),complex com1(1.1,2.2),com2(3.3,4.4),total1,total2;,total1=operator+(com1,com2);/,第一种使用方法,cout,total1.real+total1.,imag,i;,total2=com1+com2;/,第二种使用方法,cout,total2.real+total2.,imag,i;,一般而言,双目运算符,而,aa,和,bb,是类,X,的两个对象,则以下两种函数调用方法是等价的。,aa,bb,/,隐式调用,operator(,aa,bb)/,显示调用,注意:,(1)重载运算符要与原有功能类似。,(2)重载运算符,只能重载已有的运算符.程序员不能臆造新的运算符来扩充,C+,语言。,(3)类属关系运算符“.”、指针运算符“*”、作用域运算符“:”、,sizeof,运算符不能被重载。,(4)不能改变运算符的操作数个数。例如“+”,只能是两个操作数。,(5)不能改变运算符原有的优先级。,x=y-a*b;,例:用*实现两个复数相乘,编程练习,class complex,private:,double real;,double,imag,;,public:,complex(double r=0,double i=0)real=r;,imag,=i;,;,思考:,如何实现运算符重载呢?,5.3.2 友元运算符函数,运算符重载函数是在类的外部定义的,这个运算的重载函数只能访问类中的公有数据成员,而不访问类的私有数据成员。,解决办法是,:,(1),定义为它将要操作的类的成员函数,(2)是定义类的友元函数。,友元运算符函数定义的语法形式:,class x,friend,返回类型,operator,运算符(形参表),;,class complex,private:double real;double,imag,;,public:complex(double r=0,double i=0)real=r;,imag,=i;,void print();,friend complex operator+(complex co1,complex co2);,;,complex operator+(complex co1,complex co2),complex temp;,temp.real=co1.real+co2.real;,temp.,imag,=co1.,imag,+co2.,imag,;,return temp;,void complex:print(),cout,0),cout,+;,if(,imag,!=0),cout,imag,i;,void main(),complex com1(1.1,2.2),com2(3.3,4.4),total1,total2;,total1=com1+com2;,total1.print();,例:用+-*/实现两个复数类的加减乘除。,编程练习,5.3.3 成员运算符函数,成员运算符函数定义的语法形式,class X,/,返回类型,operator,运算符(形参表);,/,;,返回类型,X:operator,运算符(形参表),函数体,1.单目运算符重载,在成员运算符函数的形参表中,若运算符是单目的,则参数表为空;若运算符是双目的,则参数表中有一个操作数.,2.双目运算符重载,对双目运算符而言,成员运算符函数的形参表仅有一个参数,,它作为运算符的右操作数,,此时当前对象作为运算符的,左操作数,它是通过,this,指针,隐含地传递给函数的。,class complexprivate:double real,imag,;,public:complex(double r=0,double i=0)real=r;,imag,=i;,void print();,complex operator+(complex co2);,;,complex complex:operator+(complex co2),complex temp;,temp.real=real+co2.real;,temp.,imag,=,imag,+co2.,imag,;,return temp;,void complex:print(),cout,0),cout,+;,if(,imag,!=0),cout,imag,只能作为成员函数。,双目运算符,建议重载为友元函数。,5.3.5“+”和“-”的重载,我们知道,运算符“+”和“-”放置在变量的前面与后面,其作用是有区别的。,对于前缀方式+,ob,,可以定义成员运算符函数为:,class X,public:X operator+();,;,对于后缀方式,ob+,,可以定义成员运算符函数为:,class X,public:X operator+(,int,);,;,class complex,private:double real;double,imag,;,public:complex(double r=0,doublei,=0),real=r;,imag,=i;,void print();,complex operator+();,complex operator+(,int,);,;,complex complex:operator+(),complex temp;,temp.real=+real;,temp.,imag,=+,imag,;,return temp;,complex complex:operator+(,int,),complex temp;,temp.real=real+;,temp.,imag,=,imag,+;,return temp;,void complex:print(),cout,0),cout,+;,if(,imag,!=0),cout,imag,i;,void main(),complex com1(1.1,2.2),total1;,total1=com1+;,total1.print();,对于前缀方式+,ob,,也可以定义友元运算符函数为:,class X,public:,friend X operator+(X,;,对于后缀方式,ob+,,可以定义友元运算符函数为:,class X,public:,friend X operator+(X&ob,int,);,;,练习:,请用成员函数重载-(前缀方式),请用友元函数,重载-(后缀方式),编程练习,5.3.7下标运算符“”的重载,XY,是一个双目运算符,可以看成:,()双目运算符,X,左操作数,Y,右操作数,X5,,可被解释为,X.operator()5;,对下标运算符重载定义只能使用成员函数,其形式如下:,返回类型 类名:,operator(,形参表),/函数体,class vector,private:,int,v4;,public:,vector(,int,a1,int,a2,int,a3,int,a4),v0=a1;v1=a2;v2=a3;v4=a4;,int,operator(,int,bi);,;,void main(),vector v(1,2,3,4);,cout,v2;,class vector,private:,int,v4;,public:,vector(,int,a1,int,a2,int,a3,int,a4),v0=a1;v1=a2;v2=a3;v4=a4;,int,operator(,int,bi);,;,int,vector:operator(,int,bi),int,i=0;,if(bi=4),cout,下标错误!;,exit(1);,return vbi;,void main(),vector v(1,2,3,4);,cout,v5,endl,;,5.3.8 函数调用运算符“()”的重载,X(Y),是一个双目运算符,可以看成:,()双目运算符,X,左操作数,Y,右操作数,X(5),,可被解释为,X.operator()(5);,对函数调用运算符重载只能使用成员函数,其形式如下:,返回类型 类名:,operator()(,形参表),/函数体,通过,this,指针进行传递,class matrix,private:,int,*m;,int,row,col,;,public:matrix(,int,int,);,int,&operator()(,int,int,);,;,matrix:matrix(,int,r,int,c),row=r;,col,=c;,m=new,int,row*,col,;,for(,int,i=0;irow*,col,;i+),*(m+i)=i;,int,&matrix:operator()(,int,r,int,c),return(*(m+r*,col,+c);,main(),matrix a(10,10);,cout,a(3,4);,a(3,4)=2;,cout,a(3,4);,5.5 虚函数,静态联编:指在编译时就决定如何实现某一动作。要求在程序编译时就知道调用函数的全部信息。因此,这种联编类型的函数调用速度很快,效率高。,动态联编:指系统在运行时动态实现某一动作。采用这种联编方式,一直要到程序运行时才能确定调用哪个函数。优点是:提供了更好的灵活性、抽象性和程序易维护性。,class base,int,a;,public:base(,int,x)a=x;,void show(),cout,base:a,endl,;,;,class derive:public base,int,b;,public:,derive(,int,x,int,y):base(x)b=y;,void set(,int,y)b=y;,void show(),cout,derived:show();,bp,=,bp,-show();,注1,:声明为指向基类对象的指针,可以指向它的公有派生类对象但是不允许指向它的私有派生的对象,。,注2:允许将一个声明为,基类的指针指向其公有派生类对象,,但是,不允许将一个声明为指向派生类的指针指向其基类的对象,。,注3:声明为指向基类对象的指针,当其指向公有派生类对象时只能用它来直接访问,派生类从基类继承来的成员,,而不能直接访问公有派生类中定义的成员。,base op1,*,ptr,;,derive1 op2;,derive2 op3,*op4;,ptr,=,ptr,=,ptr,=,op4=,ptr,-set();,class base;,class derive1:private base;,class derive2:public basepublic:set();,判断正确性?,/错误,不允许将,base,类指针指向私有派生类对象,/错误,/错误,5.5.2 虚函数的定义及使用,1.虚函数的作用,在上例中,虽然基类指针指向了派生类对象,但是它所调用的函数仍然是基类对象.这说明,不管指针当前指向的哪个对象,基类对象调用的都是基类定义的函数。其原因在于普通成员的调用是在编译时静态联编的。,解决的方法是将基类中的函数成员说明为虚函数。,virtual,函数类型 函数名(形参表),函数体,class base,int,a;,public:base(,int,x)a=x;,virtual void show(),cout,base:a,endl,;,class derive:public base,int,b;,public:,derive(,int,x,int,y):base(x)b=y;,void set(,int,y)b=y;,void show(),cout,derived:show();,bp,=,bp,-show();,关键字,virtual,指示,C+,编译器,函数调用,bp,-show();,要在运行时确定要调用的函数,即要对调用进行动态联编。因此,程序在运行时根据指针,bp,所指向的实际对象,调用该对象的成员函数。,可见,虚函数同派生类的结合可使,C+,支持运行时的多态性,而多态性对面向对象的程序设计是非常重要的。,注意:虚函数与重载函数的关系,(1)当重载一个虚函数时,也就是说在,派生类中重新定义虚函数时,要求函数名、返回类型、参数个数、参数的类型和顺序与基类中的虚函数原型完全相同,。,在派生类对基类中声明的虚函数进行重新定义时,关键字,virtual,可以写也可以不写(但提倡写,)。,(2),如果仅仅返回类型不同,其余均相同,系统会给出错误信息若仅仅函数名相同,而参数的个数、类型或顺序不同,系统将它为普通的函数重载,这时虚函数的特性将丢失。,构造函数不能是虚函数,但析构函数可以是虚函数。,void main(),base d1,*,bp,;,derived d2;,bp,=,bp,-func1();,bp,-func2();,bp,-func4();,void base:func1(),cout,基类函数1;,void base:func2(),cout,基类函数2;,void base:func3(),cout,基类函数3;,void base:func4(),cout,基类函数4;,void derived:func1(),cout,派生类函数1;,void derived:func2(,int,x),cout,派生类函数2,;,class base,public:,virtual void func1();,virtual void func2();,virtual void func3();,void func4();,;,class derived:public base,public:,virtual void func1();,void func2(,int,x);,char func3();,void func4();,例5.23,/虚特性消失,/可不写,virtural,/错误,仅返回类型不同,/普通函数,例:基类定义如下,求其派生类三角形的面积,派生类圆的面积(程序要用到虚函数)。,class figure,protected:double x,y;,public:,figure(double a,double b)x=a;y=b;,virtual void,showarea,(),cout,无面积;,;,class figure,protected:double x,y;,public:,figure(double a,double b)x=a;y=b;,virtual void,showarea,(),cout,无面积;,;,class triangle:public figure,public:,triangle(double a,double b):figure(a,b),void,showarea,(),cout,三角形面积为:,showarea,();,5.5.3纯虚函数和抽象类,上例中,,figure,可以派生出三角形、矩形和圆,这个类等级中,figure,体现了一个抽象的概念,在,figure,中定义一个求面积的函数显然是无意义的。但是我们可以将其说明为虚函数,为它的派生类提供一个公共的界面,各派生类根据表示的图形不同重新定义这些虚函数,以提供求面积的各自版本。,纯虚函数的一般形式如下:,virtual,函数类型 函数名(参数表)=0;,此格式与一般的虚函数定义格式基本相同,只是在后面多了“=0”。声明为纯虚函数之后,基类中就不再给出函数的实现部分。,纯虚函数的函数体由派生类给出。,class figure,protected:double x,y;,public:,figure(double a,double b)x=a;y=b;,virtual void,showarea,()=0;/,纯虚函数,;,class triangle:public figure,public:,triangle(double a,double b):figure(a,b),void,showarea,(),cout,三角形面积为:,showarea,();,抽象类:如果一个类至少有一个纯虚函数,那么该类称为抽象类。,注意:,1、由于抽象类至少包含一个没有定义功能的纯虚函数,因此抽象类只能用作其他类的基类,不能建立抽象类对象。但可以建立抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。,判断:,figure a;,figure*p=new figure(2,3);,figure*p;,triangle a(2,3);,figure,p.,showarea,();,判断:,2、若抽象类的派生类中没有重新说明纯虚函数,则这个派生类仍然是一个抽象类。,3、抽象类中也可以定义普通成员函数或虚函数,虽然不能为抽象类声明对象,但仍然通过派生类对象来调用这些不是纯虚函数的函数。,4、不允许从具体类派生出抽象类。所谓具体类,就是不包含纯虚函数的普通类。,第,七,章,模板,Template,int,max(,int,x,int,y)return(xy)?x:y;,float max(float x,float y)return(xy)?x:y;,double max(double x,double y)return(xy)?x:y;,这些函数版本执行的功能都是相同的,只是参数类型和函数返回类型不同。能否为上述这些函数只写出一套代码么?,template,返回类型 函数名(模板形参表),/函数体,6.2.1,函数模板的声明与模板函数的生成,template,T max(T x,T y),return(xy)?x:y;,void main(),int,i1=10,i2=45;,cout,max(i1,i2),endl,;,float f1=12.5,f2=24.5;,cout,max(f1,f2),endl,;,double d1=12.345,d2=21.3456;,cout,max(d1,d2);,函数模板的声明格式如下:,(1)其中,T,为类型参数。类型参数前需要加关键字,class,,这里,class,不是类的意思,只是借用此关键字,class,表示任何类型的意思,。,(2)在使用函数模板时,关键字,class,后面的类型参数,必须,实例化,,即用实际的数据类型替代它。,上例中,分别用,max(i1,i2),max(f1,f2),max(d1,d2),将,T,进行实例化。,函数模板实现了函数参数的通用性,作为一种代码的重用机制,可以大幅度地提高设计的效率.,例,1,:已知下列主函数,void main(),int,i1=10,i2=5,i3=3;,float f1=10.0,f2=5.0,f3=3.4;,char c1=a,c2=b,c3=c;,cout,min(i1,i2,i3),endl,;,cout,min(f1,f2,f3),endl,;,cout,min(c1,c2,c3),endl,;,请定义三个数之间较小者的函数模板。,编程练习,答案:,template,T min(T x,T y,T z),T min;,min=(xy)?x:y;,return(minz)?min:z;,例2:写一个函数模板,求数组中的最大元素。使得函数调用时,数组的类型和返回类型可以是整数也可以是双精度型。,main(),int ia,=11,12,19,15;,double,da,=11.2,43.5,12.7;,cout,max(,ia,4),endl,;,cout,max(,da,3),endl,;,主函数如下:,main(),int ia,=11,12,19,15;,double,da,=11.2,43.5,12.7;,cout,max(,ia,4),endl,;,cout,max(,da,3),endl,;,template,T max(T*array,int,size),int,i,j=0;,for(i=1;iarrayj)j=i;,return arrayj;,6.3 类模板,一个类模板允许用户为类定义一种模式,使得类中的某些数据成员、某些成员函数的参数或返回值,能取任意数据类型。,格式如下:,template,class,类名,/,在类外定义成员函数时,若此成员函数中有类型参数存在,则需要在函数体外进行模板声明,并且在函数名前的类名后缀上“”.,class,cpoint,private:,int,x,y;,public:,void,setpoint,(,int,a,int,b);,void show(),cout,x=x y=y;,;,void,cpoint,:,setpoint,(,int,a,int,b),x=a;y=b;,void main(),int,a=2,b=3;,cpoint,op1;,op1.,setpoint,(a,b);,op1.show();,float p=2.2,q=3.3;,op1.,setpoint,(p,q);,op1.show();,template,class,cpoint,private:,Type x,y;,public:,void,setpoint,(Type a,Type b);,void show(),cout,x=x y=y;,;,template,void,cpoint,:,setpoint,(Type a,Type b),x=a;y=b;,void main(),int,a=2,b=3;,cpoint,op1;,op1.,setpoint,(a,b);,op1.show();,float p=2.2,q=3.3;,cpoint,op2;,op2.,setpoint,(p,q);,op2.show();,成员函数在类定义体外定义时,格式为:,template,void,类名:,函数名(,Type,参数名),/,函数体,创建对象:,类名 对象名;,类模板不代表一个具体的,实际的类,而代表着一类类。,const,int,size=10;,template,class stack,Type stacksize;,int tos,;,public:,void init(),tos,=0;,void push(Type,ch,);,Type pop();,;,template,void stack:push(Type ob),if(,tos,=size),cout,stack is full;return;,stack,tos,=ob;,tos,+;,template,Type stack:pop(),if(,tos,=0),cout,stack is empty;,return 0;,tos,-;,return stack,tos,;,int,main(),stack s1;,int,i;,s1.init();,s1.push(a);,s1.push(b);,cout,s1.pop(),endl,s1.pop(),endl,;,stackis1;,is1.init();,is1.push(1);,is1.push(2);,cout,is1.pop(),endl,is1.pop();,例,3,:建立类模板,在调用构造函数时,完成以下工作:,(,1,)提示用户输入,(,2,)让用户输入数据。,(,3,)如果数据不在预定义范围内,重新提示输入。,input,型的对象应当按以下形式定义:,input ob(“,promput,message”,min_value,max_value),其中,,promput,message,是提示输入的信息。可接受的最小值和最大值分别由,min_value,和,max_value,指定。,编程练习,template,class input,private:,X data;,public:,input(char*s,X min,X max);,;,template,input:input(char*s,X min,X max),do,cout,data;,while(datamax);,main(),input i(,输入数据:,0,10);,input c(,输入字符,A,Z);,cout,-,结束,-;,
展开阅读全文