下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C++面向对象程序设计教程(二版)讲稿第5章多态性(书P162)ー、多态性概念1、什么是多态多态是指同样的消息被不同类型的对象接收时导致完全不同的行为。或者说,多态性是指ー个名字可以定义不同的函数,这些函数执行不同功能但又类似的操作。也可以说“一个接口,多种方法”2、多态实现的分类多态从实现的角度划分为两类:(1)编译时多态(静态多态),编译过程中确定同名操作的具体对象。包括函数重载和运算符重载两种。(2)运行时多态(动态多态),程序运行过程オ动态地确定操作所针对的具体对象。包括虚函数一种。3、联编确定操作的具体对象的过程用联与实现(1)定义:联编是指计算机程序自身彼此关联的过程,也就是把ー个标识符名和一个存储地址联系在ー起的过程。(2)联编分类(i)静态庆编:联编工作在编译连接阶段完成,效率高。“名字压延”书P163)是静态联编的ー种方式,稍后讲。(ii)动态朕编:联编工作在运行阶段完成,更灵活。二、函数重载进ー步说明函数重载是静态多态的典型方式。函数重载是用同一个函数名访问ー组相关操作的函数。第2章已介绍了用函数参数个数、类型或类型顺序不同来定义ー组重载函数规则。下面补充在基类和派生类中函数重载的情况。例5.1(书P163)#include<iostream.h>classpoint 声明一个基类point(intx,y;public:point(inta,intb) 基类point有2个参数的构造函数{x=a,y=b;}doubleareaO 基类point的area()函数{return0;});classcircle:publicpoint 定义公有派生类circle,公有继承pointintradius;public:circle(intx,inty,intrad):point(x,y)/ノ派生类有3个参数的构造函数,传递给基类point构造函数{radius=rad;}doublearea() 派生类circle重新定义的area。函数{return3.1416*radius*radius;});main()(pointp(20,20);〃创建基类对象p,调用基类构造函数初始化x和ycirclec(8,8,30);创建派生类circle对象c,调用派生类circle构造函数初始化x和y和radiuscout«p.area()«endl;//执行基类point中的area。函数,因p是基类对象,输出〇cout«c.area()«endl;//执行派生类circle中的areaO函数,因c是派生类对象,输出面积值2827.44cout«c.point::area()«endl;//执行基类point中的area。函数,因调用基类名指定的area()函数,输出0return0;程序的运行结果为:02827.440通过本例得出,在基类和派生类中函数重载有如下规则:(1)重载函数参数差别,这在前面第2章已讲过规则。(2)重载函数不带参数或参数完全相同,只是属于不同类(基类或派生类),这又分成下面调用规则:(i)使用对象名区分,例中p.area。就是调用基类的area()函数,而c.area。就是调用派生类的area()函数。(ii)使用类名加“::”调用例如,point::area() 是调用基类的area()函数circle::area() 是调用派生类的area()函数(3)“名字压延”(书P163)所谓“名字压延”就是名字压缩或延伸。这是编译程序用来区分重载函数的ー种方式。“名字压延”就是把重载函数的函数名和它的参数结合起来,采用名字压缩或延伸方法创造出函数的新名字,然后在函数原型和定义及调用处均用新名字替代。于是原来没有区别的名字,现在就区分开来了。例如(书P164)有两个重载函数原型:intmyAns(floatx,intj);intmyAns(inti,charc);并用以下调用语句examl=myAns(15.3,15);exam2=myAns(45,'a');利用“名字压延”方法,在编译完成之前,相同的函数名字myAns改成如下:(这仅是示意说明,实际上不同编译程序处理各不相同)intmyAnsFLTINT(floatx,intj);intmyAnsINTCHAR(inti,charc);显然利用重载函数不同类型关键字进行压延,就得到两个不同的函数名myAnsFLTINT和myAnsINTCHARo执行语句也跟随修改为:examl=myAnsFLTINT(15.3,15);exam2=myAnsINTCHAR(45,'a');这样原来相同的函数名,现在变成不相同了。于是就会依照不同参数调用相应函数。这是在编译阶段完成的。三、运算符重载1、什么是运算符重载,为什么运算符还要重载在C++对于常用的标准类型(int、float,double等)C++5章_P5实施通常的运算+、ー、・、人…,是没有问题的。例如,intc,a=3,b=5;doublex,y=3.5,z=5.4;c=a+b; x=z-y;但如果是ー个复数类型complex(定义见P165)complexcoml,com2,total;total=coml+com2;若没有经过定义,C++目前是实现不了的。实际上,运算符是ー种操作,可以看成是一个函数,称为运算符函数。在C++运算符函数表示为operator©(•••)〇字符代表某ー个运算符。例如:operator+(…)ヽoperator-(•,,)>operator*(•••)>•••对于标准类型!nt>float>doub1e等,C++编译系统已预定义了这些类型的运算符函数。例如,intoperator+(intx,inty){return(x+y);)doubleoperator—(doublex,doubley)return(y-x);对于3+5系统会调用int类型加法运算符函数对于5.4-3.5系统会调用double类型减法运算符函数但对于复数coml+com2C++编译系统没有预定义运算符函数,所以实现不了。只能由用户自定义复数类型运算符函数。一般地用户自定义的类型,C++系统也不会预定义运算符函数,也要用户自定义相应的运算符函数。这就是为什么需要运算符重载(重新定义运算符的功能,使它既适合系统已有的功能,又适合用户自定义类型运算的功能)。2、运算符重载的实现像系统预定义哪样,一般的运算符重载也用函数实现,通常按以下方式:(1)定义ー个类(2)类内定义运算符重载函数(3)或在类内定义运算符重载函数原型声明,在类外定义运算符重载函数体因为用户自定义类型的运算符重载函数,要求能访问运算对象的私有成员,所以只能用类的成员函数或作为友元的外部函数两种形式定义运算符重载函数。3、运算符重载的规则(1)只能重载C++已有的运算符,不能自行新创运算符,例如“**”,它不是C++已有的运算符,就不能重载。C++5章_P7(2)C++已有的运算符中不能重载的运算符只有5个,它们是:类属关系运算符成员指针运算符“*”、作用域运算符“::”、条件运算符域:"及“sizeof”运算符。(3)重载之后不能改变运算符原来的优先级和结合方向。(4)重载是针对新类型数据的实际需要,对原有运算符进行适当的改造,重载的功能与原功能应相似。不能改变原运算符的操作对象个数,且至少有一个操作对象是自定义类型。4、作为友元的运算符重载函数(1)一般格式直接在类内定义重载函数格式:friend函数返回值类型operator©(形参表){〃函数体语句)类内声明重载函数原型,类外定义重载函数体格式:class类名(//•••friend函数返回值类型operator©(形参表);原型//-};
函数返回值类型operator©(形参表)类外定义函数体{〃函数体语句)(i) ”代表要重载的某个运算符。(ii)类外定义中,不能用“类名::”这是因为友元函数不是类的成员函数。(Hi)由于友元函数不是类的成员函数,所以没有this指针,全靠参数传递。若友元函数重载的是双目运算符,则参数表中需两个形参;若友元函数重载的是单目运算符,则参数表中只需ー个形参。(2)双目运算符重载当用友元函数重载双目运算符时,两个操作数都要传递给运算符函数,以复数运算符为例,复数加、减、乘、除运算参考书P169例5.3(书P169)Winclude<iostream.h>classcomplex 声明一1个基类complexpublic:complex(doubler=0.0,doublei=0.0);//基类complex有2个参数的构造函数原型voidprint(); 基类complex的print()函数原型friendcomplexoperator+(complexa,complexb);〃用友元函数实现运算符+重载函数的原型friendcomplexoperator-(complexa,complexb);〃用友元函数实现运算符一重载函数的原型friendcomplexoperator*(complexa,complexb);//用友元函数实现运算符・重载函数的原型friendcomplexoperator/(complexa,complexb);//用友元函数实现运算符/重载函数的原型private:doublereal;doubleimag;);complex::complex(doubler,doublei)〃定义基类complex构造函数(real=r;imag=i;complexoperator+(complexa,complexb)//运算符+重载用友元函数的实现(complextemp;temp,real=a.real+b.real;temp,imag=a.imag+b.imag;returntemp;)complexoperator-(complexa,complexb)//运算符一重载用友元函数的实现{complextemp;temp,real=a.real-b.real;temp,imag=a.imag-b.imag;returntemp;}complexoperator*(complexa,complexb)〃运算符・重载用友元函数的实现(complextemp;temp,real=a.real*b.real-a.imag*b.imag;temp,imag=a.real*b.imag+a.imag*b.real;returntemp;)complexoperator/(complexa,complexb)〃运算符/重载用友元函数的实现|complextemp;doublet;t=1/(b.real*b.real+b.imag*b.imag);temp,real=(a.real*b.real+a.imag*b.imag)*t;temp,imag=(b.real*a.imag-a.real*b.imag)*t;returntemp;}voidcomplex::print()基类complex的print()函数的实现(cout«real;if(imag>0)cout«"+";if(imag!=0)cout«imag«"i\n";
main(){complexAl(2.3,4.6),A2(3.6,2.8),A3,A4,A5,A6;A3=Al+A2;A4=Al-A2;A5=Al*A2;A6=Al/A2;Al.print();A2.print();A3,print();A4.print();A5.print();A6.print();return0;)程序的运行结果为:3+4.6i6+2.8i5.9+7.4i-1.3+1.8i-4.6+23i/y复数相加ん/y复数相加ん复数相减/ノ复数相乘/ノ复数相除/Z输出复数A1/ノ输出复数A2//输出复数相加结果A3//输出复数相减结果A4//输出复数相乘结果A5//输出复数相除结果A6一般而言,如果在类X中采用友元函数重载双目运算符@,而aa和bb是类X的两个对象,则以下两种函数调用方法是等价的:aa@bb; 〃表达式方式隐式调用operator©(aa,bb);〃函数调用方式显式调用运算符重载函数实现有时可直接调用类构造函数来生成一个临时对象,而不对该对象进行命名。例如(书P171)复数+的重载函数complexoperator+(complexa,complexb){complextemp;temp,real=a.real+b.real;temp,imag=a.imag+b.imag;returntemp;)可以改写为,complexoperators-(complexa,complexb){returncomplex(a.real+b.real,a.imag+b.imag);)在这里创建一个临时未命名的对象,并把临时未命名的对C++5章一P14象直接返回主调函数中。这种函数效率高。(3)单目运算符重载用友元函数重载单目运算符时,需要一个显式的操作数,例如取负运算符一重载函数例5.4(书P172)ttinclude<iostream.h>classAB 〃声明一个基类AB(public:AB(intx=0,inty=0)〃基类AB有2个参数的构造函数{a=x,b=y;}friendABoperator-(ABobj);〃用友元函数定义单目运算符“-”重载函数原型voidprint(): 基类AB的print。函数原型private:inta,b;};ABoperator-(ABobj)//用友元函数定义单目运算符“-”重载函数obj.a=-obj.a;
obj.b=-obj.b;returnobj;voidAB::print()(coutくく〃a;〃くくaくく〃)main()|ABobl(50,60),ob2;obi.print();ob2=-obi;ob2.print();return0;b=〃くくb=〃くくbくくendl;〃创建基类对象obi,ob2//对象obi调用基类print()函数输出/Z对象ob2取对象obi负值〃对象ob2调用基类print()函数输出a=50b=60a二一50b=-60对于前缀方式的单目运算符“++”,“一”一般对象参数形式重载函数无法实现。例5.5(书P173)#include<iostream.h>classcoord 声明一个基类coord(public:coord(inti=0,intj=0);//基类coord有2个参数的构造函数原型voidprint(); 基类coord的print()函数原型friendcoordoperator++(coordop);〃用友元函数定义单目运算符“++”重载函数原型private:intx,y;);coord::coord(inti,intj)(x=i;y=j;)voidcoord::print(){coutくく"x:"«x«",y:"<<y«endl;}
coordoperator++(coordop)//用友元函数定义单目运算符“++”重载函数++op.X;++op.y;++op.X;++op.y;//函数内部各项增1returnop;}main()(coordob(10,20); 创建基类对象obob.print(); 对象ob调用基类print()函数输出operator++(ob); 显式调用友元函数operator++()ob.print(); 对象ob调用基类print()函数输出++ob; 〃表达式形式调用友元函数operator++()ob.print(); 对象ob调用基类print()函数输出return0;}程序的运行结果为:TOC\o"1-5"\h\zx : 10 y : 20x : 10 y : 20x : 10 y : 20而我们希望的运算结果应是:x:10y:20x:11y:21x:12y:22显然上面程序的运算符“++”重载函数是错误的。错误的原因是友元运算符重载函数用的是类对象参数,是通过传值方法传递参数,函数体内对对象op的所有修改均无法传到函数本タI。因此,在operater++()函数中,任何内容的改变不会影响产生调用的操作数。也就是说,实际上对象x和y并未增1。而++运算符的原义是要改变操作数自身的值。为了解决这个问题,只需采用“引用参数传递操作数”就可以了。因为引用传递函数参数,实际上传递是操作数的地址,这将导致函数参数的任何改变都影响产生调用的操作数,从而保持了运算符++的原意。例5.6(书P174)#include<iostream.h>classcoord 声明—^基类coord(public:coord(inti=0,intj=0);〃基类coord有2个参数的构造函数原型C++5章一P19voidprint(); 基类coord的print()函数原型friendcoordoperator++(coord&op);〃用友元函数定义单目运算符“++”重载函数,用引用参数的原型private:intx,y;);coord::coord(inti,intj)(x=i;y=j;)voidcoord::print()(coutくく"x:"«x«",y:"<<y«endl;)coordoperator++(coord&op)//用友元函数定义单目运算符“++”重载函数,用引用参数{++op.x; 〃函数内部各项增1++op.y; 〃函数内部各项增1returnop;main()coordob(10,20); 创建基类对象obob.print(); 对象ob调用基类print()函数输出operator++(ob); 显式调用友元函数operator++()ob.print(); 〃对象ob调用基类print。函数输出++ob; 表达式形式调用友元函数operator++()ob.print。; 对象ob调用基类print。函数输出return0;}程序的运行结果为:x : 10 y : 20x : 11 y : 21x : 12 y : 22如果在X类采用友元函数重载单目运算符而明是类X的对象,则以下两种调用方法是等价的:@aa 〃表达式形式隐式调用operator®(aa)函数形式显式调用几点说明:(1)运算符重载函数operator()通常返回所操作的类类型,可使重载运算符用在复杂的表达式中。例如,可以C++5章_P21连续进行加、减、乘、除。(2)可以为同一个运算符定义几个运算符重载函数,C++编译器根据参数的个数和类型来决定调用哪个重载函数。(3)不能用友元重载函数的运算符有:=、()、[]、->5、成员函数运算符重载函数(1)类内定义重载函数格式函数返回值类型operator©(形参表){〃函数体语句)(b)类内定义重载函数原型,类外定义重载函数体class类名(函数返回值类型operator©(形参表);//-);函数返回值类型类名::operator©(形参表)〃函数体语句其中,形参表,当运算符@是单目的,则参数表为空,当运算符@是双目的,则参数表有一个形参。因为重载函数是成员函数定义后有隐含的this指针,哪ー个对象调用,this指针就指向该对象,可以作为ー个参数传递。(2)双目运算符对双目运算符,成员运算符重载函数的形参表仅有一个形参,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含传递给函数的。也就是说,这个this指针可以作为ー个隐含的参数。例5.7(书P176)是ー个类内定义原型,类外实现的复数“+,,、,一,,、“*,,、“/,,运算符成员函数。#include<iostream.h>classcomplex 声明一个基类complex(public:complex(doubler=0.0,doublei=0.0);//基类complex有2个参数的构造函数原型voidprint(); 基类complex的print()函数原型complexoperator+(complexc);//运算符+重载用成元函数的原型complexoperator-(complexc);〃运算符一重载用成元函数的原型complexoperator*(complexc);//运算符・重载用成元函数的原型complexoperator/(complexc);〃运算符/重载用成元函数的原型private:doublereal;doubleimag;);complex::complex(doubler,doublei) 定义基类complex构造函数(real=r;imag=i;)complexcomplex::operator+(complexc)〃运算符+重载用成元函数的实现complextemp;temp,real=real+c.real;temp,imag=imag+c.imag;returntemp;complexcomplex::operator-(complexc)//运算符+重载用成元函数的实现(complextemp;temp,real=real-c.real;temp,imag=imag-c.imag;returntemp;)complexcomplex::operator*(complexc)〃运算符・重载用成元函数的实现(complextemp;temp,real=real*c.real-imag*c.imag;temp,imag=real*c.imag+imag*c.real;returntemp;)complexcomplex::operator/(complexc)〃运算符/重载用成元函数的实现complextemp;doublet;t=1/(c.real*c.real+c.imag*c.imag);temp,real=(real*c.real+imag*c.imag)*t;temp,imag=(c.real*imag-real*c.imag)*t;returntemp;)voidcomplex::print()基类complex的print()函数的实现{cout«real;if(imag>0)cout«"+";if(imag!=0)cout«imag«"i\n";)main()(complexAl(2.3,4.6),A2(3.6,2.8),A3,A4,A5,A6;A3 = Al + A2 ; //复数相加A4 = Al - A2 ; //复数相减A5 = Al * A2 ; //复数相乘
A6=Al/A2;//复数相除A6=Al/A2;//复数相除Al.print();A2.print();A3,print();A4.print();A5.print();A6.print();/ノ输出复数A2//输出复数相加结果A3输出复数相减结果A4//输出复数相乘结果A5〃输出复数相除结果A6return0;)程序的运行结果为:3+4.6i6+2.8i9+7.4i-1.3+1.8i-4.6+23i1.01731+0.4865381结果与例5.3用友元重载函数结果完全相同。这个例在主函数用表达式形式调用A3=Al+A2;A4=Al-A2;A5=Al*A2;A6=Al/A2;等价于如下用显式调用重载函数:A3=Al.operator+(A2);A4=Al.operator-(A2);A5=Al.operator*(A2);A6=Al.operator/(A2);由此可见,成员运算符重载函数operator@实际上是由双目运算符左边的对象A1(当前对象)调用,尽管双目运算符函数的参数只有一个操作数A2,但另ー个操作数是对象A!通过this指针隐含地传递的。一般来说,aa和bb是类X的两个对象。则下面调用语句是等价的:aa©bb; 表达式形式隐式调用aa.operator©(bb); 函数调用形式显式调用(3)单目运算符重载对单目运算符,成员运算符重载函数的参数表中没有参数,此时当前对象作为运算符的ー个操作数。例5.8(书P179)重载单目运算符++#include<iostream.h>classcoord 声明一个基类coord(public:coord(inti=0,intj=0);//基类coord有2个参数的构造函数原型voidprint();基类coord的print()函数原型coordoperator++();〃定义单目运算符“++”重载函数原型private:intx,y;);coord::coord(inti,intj)〃定义基类coord构造函数(x=i;y=j;voidcoord::print()coutくく"x:"«x«",y:"«y«endl;)coordcoord::operator++()//定义成员运算符函数。perator++()++x; 〃函数内部各项增1++y; 〃函数内部各项增1return*this;〃返回this指针所指存储块里面的值}main()(coordob(10,20); 〃创建基类对象obob.print(); 对象ob调用基类print()函数输出++ob;〃表达式调用隐式成员运算符函数operator++()ob.print(); 对象ob调用基类print()函数输出ob.operator++(); 显式调用成员运算符函数operator++()ob.print();〃对象。b调用基类print。函数输出return0;)程序的运行结果为:x:10y:20x:11y:21x:12y:22由于所有成员函数都有一个this指针,它是指向对象的指针。因此,任何对对象私有数据的修改都将影响实际调用运算符的对象。采用成员函数重载单目运算符时,下面2种调用方式是等价的:@aa; 〃表达式形式隐式调用aa.operator©(); 〃函数调用形式显式调用成员运算符函数operatorゆ所需的ー个操作数由对象aa通过this指针隐含地传递,因此在参数表上没有参数。(4)作为成员运算符重载函数与友元运算符重载函数比较(a)双目运算符重载函数一般可任意采用成员或友元函数,但对于运算符所需的操作数(尤其是第一操作数,即左操作数)是标准数据类型(即可以隐式类型转换),这时运算符必须用友元函数。例如(书P180)类AB中若采用成员函数重载“+”运算符(书P172例5.4)〇ABAB::operator+(intx){ABtemp;Temp,a=a+x;Temp,b=b+x;returntemp;)若对类AB的对象ob要做赋值运算和加法运算,下面语句没问题:ob=ob+100;(或ob=ob.operator+(100))由于对象ob是运算符“+”的左操作数,所以调用“+”运算符重载函数,通过this指针把ー个对象作为参数参加运算。最终,把ー个整数100加到了对象op的元素上。如果用如右面语句: ob=100+ob;由于左部操作数100不是AB类对象,不能进行取成员”操作,这是错误的,ob=15&<d^erator(ob)如果用友元函数来重载运算符“+”函数,就能消除左操作数是标准数据类型带来的问题。例5.9(书P181)#include<iostream.h>classAB //声明一个基类AB{inta,b;public:AB(intx=0,inty=0);/,基类AB有2个参数的构造函数原型friendABoperator+(ABob,intx);〃用友元函数定义双目运算符“+”重载函数原型(整型参数在右边)friendABoperator+(intx,ABob);//用友元函数定义双目运算符“+”重载函数原型(整型参数在左边)voidshow(); 基类AB的show。函数原型I;AB::AB(intx,inty) 基类AB有2个参数的构造函数{a=x,b=y;}ABoperator+(ABob,intx)//用友元函数定义双目运算符“+”重载函数,整型参数在右边ABtemp;temp,a=ob.a+x;temp,b=ob.b+x;returntemp;)ABoperator+(intx,ABob)/,用友元函数定义双目运算符“+”重载函数,整型参数在左边{ABtemp;temp,a=x+ob.a;temp,b=x+ob.b;returntemp;)voidAB::show()(coutくく"a="くくaくく""«"b="«b<<endl;main()ABobl(30,40),ob2; //创建基类对象obi,ob2ob2=obi+30; //对象表达式赋值ob2.show(); 对象ob2调用基类show()函数输出ob2=50+obi; Z对象表达式赋值ob2.show(); 对象ob2调用基类show()函数输出return0;)程序的运行结果为:a=60b=70a=80b=90(b)成员和友元函数都可以用习惯的表达式形式调用,也可以用函数调用形式调用(参看书P182表5.2)。(c)究竟用成员函数还是友元函数重载运算符为好,没有固定规定。参考书P182的说明。(5)“++”与的重载“++”与“-ノ的重载的前缀形式++ob(先加或先减后用)和后缀形式ob++(先用后加或后减)其作用有区别。在运算符重载函数中如何区别呢?可采用括号关键字int来区分。如书P182,以“++”例,(“一”类似)C++5章_P35设,ob为X类对象。对于前缀方式++ob,可以用运算符函数重载为:ob.operator++(); 成员函数重载或friendoperator++(X&ob); 友元函数重载对于后缀方式ob++,可以用运算符函数重载为:ob.operator++(int); 成员函数重载或friendoperator++(X&ob,int); 友元函数重载调用时,参数int一般被传递给值〇。例如:用成员函数重载方式:classX{••public:••Xoperator++(); 前缀方式Xoperator++(int); 后缀方式•••};main(){Xob;•••++ob; 隐式调用ob. operator++()ob++; 隐式调用ob. operator++(int)ob.operator++ (); 显式调用ob. operator++()ob.operator++ ( 0);显式调用ob. operator++(int)•••)也可以用友元函数重载为:classY(••public:••friendoperator++(Y&); 前缀方式friendoperator++(Y&,int);后缀方式};main()Yob;•••++ob; 隐式调用operator ++(Y &)ob++; 隐式调用operator ++(Y &,int)operators-+ (ob); 显式调用operator ++(Y &)operator++(ob,0);显式调用operator++(Y&,int)•••)例5.10(书P184)#include<iostream.h>#include<iomanip.h>classover 〃声明一个基类over(intil,i2,i3;public:voidinit(intII,int12,int13); 初始化函数原型voidprint(); 输出数据成员函数print()原型overoperator++();〃成员函数重载“++”(前缀方式)原型overoperator++(int);//成员函数重载“++”(后缀方式)原型friendoveroperator一(over&);
〃友元函数重载“一”(前缀方式)原型friendoveroperator—(over&,int);//友元函数重载“一”(后缀方式)原型C++5章_P38);voidover::init(intII,int12,int13) 定义初始化函数{il=II;i2=12;i3=13;}voidover::print() 定义输出数据成员函数cout«〃il:〃«il«"i2:"«i2くく"i3:"«i3«endl;overover::operator++(){++il;++i2; ++i3;return*this;)overover::operator++(){++il;++i2; ++i3;return*this;)overover::operator++(int){il++;i2++; i3++;return*this;)overoperator—(over&op)〃定义成员函数重载“++”(前缀方式)〃定义成员函数重载“++”(后缀方式)〃定义友元函数重载”(前缀方式){一op.il:一op.i2; 一op.i3;returnop;
)overoperator—(over&op,int) 定义友元函数重载“一”(后缀方式)C++5章_P390P.il—;op.i2一;op.i3一;returnop;0P.il—;op.i2一;op.i3一;returnop;)main()(overobjl,obj2objl.init(4,obj2.init(2,obj3.init(8,obj4.init(3,++objl;obj2++;一obj3;obj4——;,obj3,obj4,9); /,调用初始化函数给对象obj2赋初值,8); /,调用初始化函数给对象obj3赋初值,7); /,调用初始化函数给对象obj4赋初值隐式调用overoperator++()隐式调用overoperator++(int)隐式调用overoperator--(over&)隐式调用overoperator--(over&,int)
objl.print();obj2.print();obj3.print();obj4.print();cout«objl.operator++();obj2.operator++(0);operator—(obj3);operator一(obj4,0);objl.print();obj2.print();obj3.print();obj4.print();调用print。调用print。输出objl'/显式函数调用形式,意为++objl//显式函数调用形式,意为obj2++//显式函数调用形式,意为一obj3〃显式函数调用形式,意为obj4一调用print。输出objl程序的运行结果为:il:5i2:3i3:6il:3i2:6i3:10il:7i2:2i3:7il:2i2:5i3:6il:6i2:4i3:7il:4i2:7i3:11il:6i2:1i3:6il:1i2:4i3:5说明:(i)运算符都对单值操作产生影响的,因此成员运算符函数重载通常返回this指针。(ii)由于友元函数不是成员函数,所以没有this指针,不能引用this指针所指的对象。使用友元函数应该采用引用参数传递数据。(iii)前缀和后缀方式的函数内部语句可以相同,也可以不同,取决于编程者的要求。(6)赋值运算符的重载如果用户没有自定义的赋值运算符重载函数,系统将自动生成一个默认的重载函数。例如(书P186)C++5章_P42类名&类名::operator=(const类名&source)(〃成员间赋值)对于已创建的两个对象obi和ob2则表达式obi=ob2;就调用默认的赋值符重载函数,将对象ob2的数据成员逐域拷贝到obi中。通常默认函数能胜任工作,但对于含有指针成员的问题会出现“指针悬挂”的问题。例5.11(书P186)#include<iostream.h>#include<string.h>classstring 〃声明一个基类string{char*ptr;public:string(char*s) 基类string有1个参数的构造函数{ptr=newcharEstrlen(s)+1];strcpy(ptr,s);
"string(){deleteptr;}voidprint(){cout«ptr«endl;});voidmain()(stringpl("book");(stringp2("pen");p2=pl;cout«"p2:";p2.print();cout«"pl:";pl.print();〃创建基类对象pl,置初值"book〃创建基类对象P2,置初值"〃创建基类对象pl,置初值"book〃创建基类对象P2,置初值"pen"〃对象pl赋值给p2//输出p2的值,后自调用析构函数删除〃〃输出P1的值程序运行结果是不正确的。因为执行到赋值语句P2=pl;时,实际是使两个指针P2与pl指向new开辟的同一个存储块。当p2的生存期(内层花括号范围)结束时,系统自动调用析构函数将这一存储块撤消。这时即使P1仍存在,其所指的存储块已无法访问,这就是“指针悬挂”的问题。可能就产生成严重的错误,下图5.1(书P187)分析这个问题。(a)执行p2二pl之前动态存储块2动态存储块1动态存储块2动态存储块1(b)执行p2=pl之后动态存储块1动态存储块1(c)p2的生命周期结束后?「ーー「已撤消动态存储块1[一,必须重载赋值运算符,使得对目标对象数据成员指针的赋值是把原对象指针ptr所指向的内容传递给它,而不是简单地传递指针值。例5.12(书P188)#include<iostream.h>#include<string.h>classstring 声明一个基类string{char*ptr;public:string(char*s) 基类string有1个参数的构造函数(ptr=newcharEstrlen(s)+1];strcpy(ptr,s);)string(){deleteptr;}voidprint(){cout«ptr«endl;}string&operator=(conststring&);〃赋值运算符重载函数原型);string&string::operator=(conststring&s)//赋值运算符重载函数实现{if(this=&s)return*this: 〃防止s==s的赋值deleteptr; 释放掉原存储区域ptr=newchartstrlen(s.ptr)+1]; 重新分配新存储区域strcpy(ptr,s.ptr); 字符串内容拷贝return*this;)voidmain(){stringpl("book"); 〃创建基类对象pl,置初值"book"{stringp2("pen"); 创建基类对象p2,置初值"pen"p2=pl;/ノ对象pl赋值给p2,用自定义赋值运算符重载函数实现cout«"p2:";p2.print(); //输出p2的值,后自调用析构函数删除)cout«"pl:";pl.print(); 〃输出pl的值)程序的运行结果为:p2:bookpl:book五、虚函数(书P200)虚函数是重载的另ー种重要形式,是ー种动态的重载方式,提供ー种更为灵活的多态性机制。函数允许函数调用与函数体之间的联系在运行时オ建立,也就是在运行时ォ决定执行哪ー个函数,即所谓动态联编。1、基类指针与派生类对象地址在第4章根据赋值兼容规则,指向基类对象的指针可以指向它的公有派生类的对象。因此,C++中把公有派生类对象地址赋给基类指针变量是可以的。例如,classB(public:inti; 〃基类B的公有数据成员ifloatf; 〃基类B的公有数据成员f};classD:publicB 基类B的公有派生类D(public:charch; 派生类D新增公有成员chintj; 〃派生类D新增公有成员j};
voidmainO 外部函数{Dd: 〃公有派生类D创建对象dB*b=&d;〃派生类对象d的地址赋给基类指针bb->!=12;〃给b所指i存储块赋值12)在函数main。中,派生类对象d的地址赋给了基类指针b,并且用此指针修改派生类继承基类的公有数据成员i的值为12是正确的。为什么可以这样做?因为C++的继承是采用“复制继承”方式,也就是派生类的对象要为继承来的基类的所有成员分配存储空间,然后再分配派生类新增成员的存储空基类B的成员派生类新增成员间。 基类B的成员派生类新增成员本例如右图。当把基类B指针当把基类B指针b指向派生类对象后,由于派生类中继承来的基类成员的结构和顺序都与原基类完全相同。可以认为i和f重新组成了基类B的对象。b指向此对象的首地址,因而b可以随意访问这个基类对象成员,实际上b访问的是派生类对象的成员,它访问了派生类对象成员中从基类继承来的成员。(a)但是用基类指针b指向派生类新增成员就不行。例,b->ch; 〃错误b->j; 〃错误(b)如果私有派生,派生类对象地址赋给基类指针是非法的。因为派生类中基类成员是私有的。2、基类指针用于派生类基类指针只能用于公有派生类中由基类继承来的成员(函数),不能指向派生类新增的成员(函数)。例5.20(书P200)#include<iostream.h>classmy_base 声明一个基类my_base(inta,b;public:my_base(intx,inty) 基类my_base有2个参数的构造函数(a=x;
voidshow()定义基类voidshow()定义基类my_base的输出函数show()cout«"my_base \n;coutくくaくく〃“くくbくくendl;));classmy_class:publicmy_base定义基类my_base的公有派生类my_class(intc;public:my_class(intx,inty,intz):my_base(x,y){c=z;} 派生类my_class的构造函数voidshow() 定义派生类my_class的输出函数show。(cout«"my_class \n";cout«"c="«c«endl;});voidmain()my_basemb(50,50),*mp; 〃mb是基类对象,mp是基类指针mp=&mb; 基类指针mp指向基类对象mbmp->show(); 操作由基类的show。函数my_classme(10,20,30); me是派生类对象mp=&mc;/ノ派生类对象me的地址赋给基类指针,基类指针mp指向派生类对象memp->show();//操作派生类show。函数,实际上操作由基类继承来的基类成员函数show()手基类指针不能访问派生类新增的成员。例,书P202classA{//-public:voidprintl();};classB:publicA{//-public:voidprint2();};voidmain(){Aopl,*ptr;定义基类A的对象opl和指针ptrBop2; 〃定义派生类B的对象op2ptr=&opl;将指针ptr指向基类A的对象oplptr->printl():调用基类函数printl()ptr=&op2; 将指针ptr指向派生类B的对象op2ptr->printl();〃正确,调用对象op2从基类继承来的成员函数printl()ptr->print2();〃错误,基类指针Ptr不能访问派生类新增的print2())若想访问其公有派生类的特定成员,可以将基类指针用显式类型转换为派生类指针。((B*)ptr)->print2();外层的括号表示对Ptr的强制转换而不是返回类型。3、虚函数的概念及定义(1)为什么引入虚函数书P200例5.20中尽管在基类my_base定义了成员函数show。,在公有派生类my_class又定义了成员函数show(),但利用基类指针mp尽管赋了派生类对象me的地址,企图调用派生类show。函数并不成功。执行结果(书P201)仍是调用了基类的show。函数结果。如果把基类和派生类的show()函数被声明为虚函数,情况就不同了。就可以用基类指针通过赋了派生类对象地址可以调用派生类同名虚函数。例5.21虚函数即虚拟函数(virtualfunction)是指某个函数基类中被声明为virtua!函数,该函数与同名函数可在派生类重新定义痂3〇由基类指针作为单ー接口进行调用。(2)虚函数定义格式virtual函数返回值类型函数名([参数表])(〃函数体)在基类定义虚函数可以在类内定义,也可以类内声明函数原型,在类外定义函数体(此时不必重写关键字virtual)〇C++5章_P55虚函数可以在ー个或多个派生类中被重新定义(此时就不必重写关键字virtual)。在派生类中重新定义时,其函数原型,包括返回值类型、函数名、参数个数、参数的类型及其顺序都必须与基类中的原型完全一致。例5.22(书P204)基类是虚函数Winclude<iostream.h>classparent 〃声明一个基类parent{protected:charversion;public:parent() 基类parent有地个参数的构造函数{version二'A'; }virtualvoidprint() 定义虚函数print。(coutくヘ\nTheparent,version«version;));classderivedl:publicparent 定义派生类derivedl(private:intinfo;public:derivedl(intnumber) 派生类derivedl的构造函数原型{info=number;version= 1,;)voidprint() 派生类derivedl重新定义的print()虚函数(cout«”\nThederived1info:"<<infoくくversion*«version;));classderived2:publicparent 定义派生类derivedl(private:intinfo;public:derived2(intnumber) 派生类derivedl的构造函数原型{info=number;version='2';voidprint()〃派生类derived2重新定义的print()虚函数voidprint()coutくく\nThederived2info:"<<infoくくversion«version;cout«endl;});voidmain()(parentob,*op; mp是基类指针op=&ob;op->print();derivedldl(3);op=&dl;op->print();derived2d2(15);op=&d2;op->print();/ノ操作派生类showO函数,实际上操作由基类继承来的showO函数)例5_22」基类有非虚函数
几点说明:(a)在基类中,用关键字virtua!可以将其public或protected部分的成员函数声明为虚函数。友元、静态成员函数则不可以。(b)虚函数在派生类被重新定义时,其函数原型与基类中的函数原型必须完全相同。(c)在派生类对基类中声明的虚函数进行重新定义时,关键字virtual可以写也可以不写。但容易引起混淆时最好写上。如果派生类没有用virtual声明为虚函数,系统按以下原则判断该成员函数是否是虚函数。视其与基类虚函数是否返回值类型、函数名、参数个数、参数的类型及其顺序完全一致。(d)定义了虚函数后,在主函数中如果用对象名”运算符可以调用虚函数。例如,dl.print()可以调用虚函数derived::print。,但这种调用是在编译时进行的静态联编,就没有充分利用虚函数运行时的特性。(e)一个虚函数无论公有派生多少层,仍然保持其虚函数的特性。(f)只有类的成员函数オ可以定义为虚函数。友元、静态成员函数不可以,因为虚函数调用要靠特定的对象来决定该激活哪个函数。
但虚函数可以在另ー类中被声明为友元函数。(g)构造函数不能定义成虚函数,但析构函数可以定义成虚函数。(3)虚函数与重载函数在ー个派生类中重新定义基类的虚函数是函数重载的另ー种形式,但它不同于一般的函数重载。普通函数重载其函数参数或参数类型或类型顺序必须不同。编译时以这些不同静态联编确定执行哪ー个函数。派生类中重新定义虚函数时,要求函数名、返回值类型、参数个数、参数的类型及其顺序都要与基类的虚函数原型完全相同。编译时,不是按照静态联编生成“调用此函数的版本”,而是只“为它们生成一个虚函数表(表中存放与此函数同名、同参数、同返回值的虚函数的入口地址)”。程序执行时,再根据实际对象的类型(基类型或派生类型)查虚函数表,找出相应版本的虚函数オ执行它。这就是动态联编过程。若派生类中没有重新定义基类的虚函数,而基类指针又赋了派生类的对象地址,执行该虚函数功能时C++系统会引导程序执行派生类内继承下来基类中的虚函数。
例5.23(书P206)#include<iostream.h>classbase(public:virtualvoidfund();virtualvoidfunc2();virtualvoidfunc3();voidfunc4(););classderived:publicbase声明一个基类声明一个基类base〃定义虚函数funcl()原型〃定义虚函数func2()原型〃定义虚函数func3()原型〃定义基类普通成员函数funclO原型定义派生类derivedvirtualvoidfunclO;定义虚函数fund()原型,这里可不写virtualvoidfunc2(intx): 〃作为普通函数重载,虚特性消失//charfunc3(); /,错误,因为只有返回类型不同,应删去voidfunc4(); 〃是普通函数重载,不是虚函数);voidbase::funcl() 定义基类虚函数funcl(){cout<<" basefuncl \n;}
voidbase::func2(){coutくく basefunc2 ヽn”;)voidbase::func3(){coutくく basefunc3 \n;}voidbase::func4(){cout«" basefunc4 \n;}voidderived::fund()//定义基类虚函数func2()〃定义基类虚函数func3()〃定义基类成员函数func4()//定义基类虚函数func2()〃定义基类虚函数func3()〃定义基类成员函数func4()//重新定义派生类虚函数fund()voidderived::func2(intx)〃定义派生类重载函数func2(){cout«” derivedfunc2 \n";}voidderived::func4()〃定义派生类重载函数func4()voidderived::func4()〃定义派生类重载函数func4(){cout«” derivedfunc4 \n”;}voidmain()voidmain(){basedl,*bp;derivedd2;bp=&d2;bp->fund();bp->func2();〃创建基类对象dl,指针bp〃创建派生类对象d2//把派生类对象d2的地址赋给基类指针bp调用derived::funcl()函数调用base::func2()函数//(不是调用derived::func2(),因为重定义时不是虚函数)bp->func4();〃调用base::func4()函数(引导调用基类成员函数)(4)多继承与虚函数在多继承情况下同名函数有不同的继承路径,所以呈现不同的性质。例5.24(书P208)#include<iostream.h>classbasel 〃声明一个基类basel{public:virtualvoidfun() 定义虚函数fun(){coutくく baselvirtual \n";});classbase2 声明一个基类base2{public:voidfun() 定义普通成员函数fun(){cout«* base2notvirtual \n";});classderived:publicbasel,publicbase2〃定义派生类derived是双基派生public:voidfun() 定义虚函数fun(),因为它是基类basel中定义的{coutくく derivedvirtual \n”;});voidmain()(basel*ptrl: 定义指向基类basel的指针ptrlbase2*ptr2; 定义指向基类base2的指针ptr2derivedobj3; 定义派生类derived的对象obj3ptrl=&obj3;基类base!的指针ptrl指向派生类derived的对象obj3ptrl->fun();因为fun()为虚函数,所以调用派生类derived的fun()ptr2=&obj3;基类base2的指针ptr2指向派生类derived的对象obj3ptr2->fun();因为ptr2为base2的指针,而且fun()为非虚函数,//因此调用基类base2的fun())这里派生类derived是基类basel和base2双基派生的。所以同名函数fun()有不同的继承路径。对于basel的派生路径,由于fun()在基类定义是虚函数,所以基类指针ptrl指向派生类对象obj3时访问派生类的fun()是虚函数derived::fun()〇而基类base2由于fun()是一般成员函数,所以只是一个重载函数,当声明指向base2的指针指向derived的对象obj3时,函数fun()是重载函数。Ptr2->fun()只是调用由基类继承来的base2::fun()(5)虚函数举例书P209例5.25(书P209)应用C++动态多态性,计算三角形、矩形和园的面积。#include<iostream.h>classfigure 声明一个基类figure{protected:doublex,y;public:figure(doublea,doubleb) 定义构造函数{x=a;y=b;}virtualvoidshow_area() 定义虚函数show_area(),作为公共接口{coutく、Noareacomputationdefined^;coutくくforthisclass.\n”;});classtriangle:publicfigure 定义三角形派生类public:triangle(doublea,doubleb):figure(a,b)〃定义构造函数给基类传递参数{} 〃空函数体virtualvoidshow_area()〃重新定义虚函数show_area(),用作求三角形面积{cout«”Trianglewithheight"<<x;cout«"andbase"«y«"hasanareaof";cout«x*y*0.5«endl;));classsquare:publicfigure 定义矩形派生类(public:square(doublea,doubleb):figure(a,b)〃定义构造函数给基类传递参数{} //空函数体virtualvoidshow_area()〃重新定义虚函数show_area(),用作求三角形面积{cout«"Squarewithdimension"«x;cout«"*"«y«"hasanareaof";cout«x*y«endl;);classcircle:publicfigure 定义园形派生类(public:circle(doublea):figure(a,a)〃定义构造函数给基类传递参数{} //空函数体virtualvoidshow_area()〃重新定义虚函数show_area(),用作求三角形面积{coutくくCirclewithradius”くくx;cout«"hasanareaof";cout«x*x*3.1416«endl;});main()(figure*p; 定义指向基类figure的指针ptrianglet(10.0,6.0); 创建三角形对象tsquares(10.0,6.0); 创建矩形对象scirclec(10.0); 创建园形对象cP=&t: 〃基类指针赋给三角形派生类对象地址p->show_area(); 调用虚函数show_area()计算三角形面积P=&s; //基类指针赋给矩形派生类对象地址p->show_area(); 调用虚函数show_area()计算矩形面积P=&c; //基类指针赋给园形派生类对象地址p->show_area(); 调用虚函数show_area()计算园形面积return0;)4、纯虚函数和抽象类(1)为什么引入纯虚函数基类往往表示一般性抽象的概念,并不与具体的事物相联系。如例5.25(书P209)基类figure(形状)只是表示一个封闭的图形,没有具体指明是什么图形,要计算这种抽象图形面积是不可能的。所以基类的虚函数的函数体是空的。它只为派生类提供ー个公共接口,由各派生类根据具体的几何图形重新定义虚函数计算具体图形的面积。
对于这种在基类中无具体功能需要实现的函数,能碓基类中只说明函数原型作为派生类的公共接口,由派生类再给出函数的具体实现?C++就引入纯虚函数。(2)纯虚函数的定义纯虚函数是一个在基类中说明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的虚函数版本。纯虚函数的定义格式:virtual函数返回值类型函数名([形参表])=0;此格式与一般虚函数成员的原型在书写格式上基本相同,不同的仅是在最后用“二0”代替函数体部份。声明为纯虚函数之后,基类中就不再给出函数的实现部分(即函数体)。
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 银行合规管理制度实施成效
- 高速公路安全行车管理制度
- 新学期美术教学工作计划(23篇)
- 挑战2024演讲稿(31篇)
- 六年级下册各具特色的民居课件
- 福建省泉州市惠安县2023-2024学年七年级上学期期末考试数学试卷(含解析)
- 顾客服务政策的设计
- 7.2《归园田居(其一)》课件 2024-2025学年统编版高中语文必修上册
- 福州七中2025届高考英语一模试卷含解析
- 公司金融课件版
- 《测绘工程产品价格》和《测绘工程产品困难类别细则》
- 生产现场定置管理规定区域划分、标识牌、工具摆放标准
- 接口类验收报告
- 关于公寓物业管理实施方案
- 母婴保健技术资格证考试试题及答案
- 《好天气和坏天气》课件
- (交通运输)铁路军事运输教案
- 四年级劳动教育-种植方案(课件)
- 课件我的文化班
- 隧道支护安全技术交底书
- GB∕T 22063-2018 显微镜 C型接口
评论
0/150
提交评论