多态性与重载_第1页
多态性与重载_第2页
多态性与重载_第3页
多态性与重载_第4页
多态性与重载_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

多态性与重载第1页,共19页,2023年,2月20日,星期四6.1程序的多态性多态性指在程序中同一符号或名字在不同情况下具有不同解释的现象(polymorphism)。在面向对象程序设计语言中,由程序员设计的多态性有两种基本的形式:编译时多态性和运行时多态性。编译时多态性是指在程序编译阶段即可确定下来的多态性,主要通过使用重载机制获得,重载机制包括函数重载和运算符重载两大类。函数重载允许程序员用相同的名字定义两个或更多的C++函数,使得语义非常相似的函数可以用同样的标识符来命名。运算符重载允许程序员重新定义C++语言已有的运算符,以一种更自然的方式使用程序员自己定义的类的类型。运行时多态性是指必须等到程序运行期间才能确定的多态性,主要通过继承结合动态绑定获得。要产生运行时多态性必须先设计一个类层次,然后在某些类中使用虚函数。虚函数与普通函数的区别在于函数名字与函数实体的绑定方式不同,普通函数使用的是静态绑定,而虚函数使用的是动态绑定。第2页,共19页,2023年,2月20日,星期四6.2函数重载在C++语言中,只要在声明函数原型时形式参数的个数或对应位置的类型不同,两个或更多的函数就可以共用同一个名字。这种在同一作用域中允许多个函数使用同一个函数名的措施称为重载(overloading)。函数重载是C++程序获得多态性的途径之一。C语言不支持重载,所以每个函数都必须具有唯一的一个名字。如C语言的数学函数库中提供了三个函数求绝对值:abs()、labs()和fabs()。尽管这些函数完成的任务非常相似,但必须定义为三个函数。而C++中只需使用abs()来实现,编译程序可以根据实际参数相应位置的类型选择调用哪一个版本的重载函数。第3页,共19页,2023年,2月20日,星期四函数重载的注意事项:返回值类型不能用于区分重载如:intget_value(intindex);doubleget_value(intindex);这两个函数不是重载函数,编译程序认为这是对一个函数的重复说明,因为两个函数的形式参数个数与对应位置的类型完全相同。用typedef定义的类型不能用于区分重载如:typedefdoubleMONEY;doublecalculate(doubleincome);MONEYcalculate(MONEYincome);由typedef定义的类型别名并没有真正创建一个新的类型,所以不能用于定义重载函数形参的数据类型。不同参数传递方式不能用于区分重载如:voidfunc(intvalue);voidfunc(int&value);不同参数传递方式也无法区别重载函数,因此如此的用法也是错误的。第4页,共19页,2023年,2月20日,星期四重载的二义性:重载的二义性(ambiguity)是指C++编译程序无法在多个重载函数中选择正确的函数进行调用。这些二义性错误是致命的,因而编译程序无法生成目标代码。函数重载的二义性主要源于C++语言的隐式类型转换与缺省参数。在函数调用时,编译程序将按以下规则选择重载函数:如果函数调用的实际参数类型与一个重载函数的形式参数类型完全匹配,则选择该重载函数;如果找不到与实际参数类型完全匹配的函数原型,但如果按照类型转换规则,可将一个类型转换为其他类型后,能找到完全匹配的函数类型,编译程序选择调用该重载函数,如:

intfunc(doubled);……cout<<func(‘A’);虽然没有声明函数原型intfunc(char),但函数调用func(‘A’)并不会产生任何问题,这是因为编译程序自动将字符‘A’转换为double类型,然后调用函数intfunc(double)。第5页,共19页,2023年,2月20日,星期四隐式类型转换引起的二义性如:floatab(floatx){return(x>=0?x:-x);}doubleab(doublex){return(x>=0?x:-x);}intmain(intargc,char*argv[]){cout<<ab(3.14)<<"\n";cout<<ab(-5)<<"\n";getchar();return0;}程序定义了两个重载函数ab()用于计算float和double类型数据的绝对值,主函数中第一次调用-3.14时,由于实参是double类型,因而不存在二义性问题,但调用-5时,实参是int类型,则到底要转换成float还是double就会存在二义性问题。第6页,共19页,2023年,2月20日,星期四使用缺省参数引起的二义性在重载函数中使用缺省参数也可能引起二义性。如:#include<iostream.h>intfunc(inti){returni;}intfunc(inti,intj=10){returni*j;}intmain(intargc,char*argv[]){cout<<func(3,4)<<"\n";cout<<func(5)<<"\n";getchar();return0;}当调用func(5)时,编译程序无法确定是调用单个int类型参数的版本,还是以缺省参数调用两个int类型参数的版本,因而编译时会产生二义性错误。第7页,共19页,2023年,2月20日,星期四构造函数重载:构造函数可能是C++应用函数重载最多的地方,因为我们设计一个类时总是希望创建对象的同时能以多种方式初始化对象的内部状态,而构造函数只能有一个名字,即该类的名字。构造函数重载时,形式参数的主要形式有三种:不带参数,采用某个缺省值作为参数;带普通类型(数据成员的类型)的参数(可能有缺省值);采用指定值来初始化创建的新对象;以一个已知的该类对象作为参数,用一个已知对象对新创建的对象进行初始化。第8页,共19页,2023年,2月20日,星期四拷贝构造函数在一个类声明中,以类类型本身作为形式参数且参数传递形式为按引用调用的构造函数称为拷贝构造函数(copyconstructor)。拷贝构造函数是一种特殊的构造函数。C++语言引入拷贝构造函数主要有三个作用:声明对象时使用另一个对象来初始化将对象以按值调用方式作为参数传递给一个函数生成临时对象作为函数的返回值拷贝函数的定义形式:CLASS_NAME::CLASS_NAME(constCLASS_NAMEobject){……//函数体}实验四中要求的构造函数COMPLEX(constCOMPLEX&other)的形式就是拷贝函数的一个例子。第9页,共19页,2023年,2月20日,星期四函数按值调用传递参数产生的问题将对象作为参数传递时应尽量使用对象指针而不是对象本身。然而,对象也可以像其他基本类型的数据一样以按值调用方式直接作为参数来传递,为什么不直接使用对象作为参数?这是因为在C++语言中会产生一些意想不到的问题。当作为实参的对象传递给函数时,传递的是该对象的一个副本而不是对象本身传递给函数,因而函数体内对参数所做的修改并不影响对象本身。但当对象所属的类有构造函数与析构函数时。见实例。第10页,共19页,2023年,2月20日,星期四classTEST{public:TEST(inti){Message("Constructing!");value=i;return;}~TEST(){Message("Destructing!");return;}intget(){returnvalue;}private:intvalue;};voiddisplay(TESTobj)//对象作为函数的参数,按值调用方式传递{Message(obj.get())return;}第11页,共19页,2023年,2月20日,星期四从上例中我们可以发现,程序多调用了一次析构函数,为什么会多调用一次析构函数?这是因为按值传递时,首先会创建对象obj的另一个副本,然后将该副本传递给函数display(),函数调用结束后该副本本撤消。但创建对象副本时并没有调用构造函数,因为调用构造函数会初始化对象的状态。而在撤消对象副本时却调用了析构函数,因为对象副本占用的内存空间需要回收。当构造函数与析构函数中利用new和delete动态创建与撤消对象时,可能造成严重的内存管理问题。因而在真正需要对象作为参数时,必须有响应的解决方法。通常采用的解决方法有两种:一是将函数display()的参数传递方式改为按引用调用,这时就不会生成对象副本;二是为类TEST定义一个拷贝构造函数,生成对象副本时将调用拷贝构造函数进行初始化。第12页,共19页,2023年,2月20日,星期四对象作为函数返回值产生的问题当一个对象由函数返回时,编译程序将自动创建一个临时对象来保存函数的返回值,这个临时对象才是函数真正返回的对象。函数调用表达式结束后,该临时对象就被撤消。同样,在有些情况下,这个临时对象可能引起一些意想不到的副作用。见实例。从结果可以看出;第一个构造函数调用是main()中声明对象NAMEmyname引起的,第二个构造函数是函数get_name()中NAMEtemp_obj调用的。析构函数调用两次后,系统就死机了。这是因为,第一次调用析构函数,是将temp_obj中用set()分配的内存释放掉,而第二次调用析构函数时释放的是用于保存函数返回值的临时对象,由于myname对象还指向同样位置,因此结果就没地方保存,出现内存冲突错误。这里产生问题的原因也是由于创建函数返回值的临时对象没有调用构造函数,而只在撤消时调用析构函数。应尽量使函数返回对象指针,但真正需要返回对象时必须使用拷贝函数解决这些问题。所有的类都必须有一个拷贝函数。如果一个类没有定义拷贝函数,那么编译程序将自动产生一个公有的缺省拷贝函数。缺省拷贝函数使用浅复制方式(逐位复制)利用另一个对象来初始化新创建对象的状态,如果我们需要另外的复制策略就需要自己设计一个拷贝构造函数。第13页,共19页,2023年,2月20日,星期四classNAME{public:NAME(){ShowMessage("Constructing!");string=NULL;return;}~NAME(){ShowMessage("Destructing!");if(string!=NULL)deletestring;return;}voidshow(){ShowMessage(string);}voidset(char*s){string=newchar[strlen(s)+1];strcpy(string,s);return;}private:char*string;};NAMEget_name(){NAMEtemp_obj;temp_obj.set(Form1->Edit1->Text.c_str());returntemp_obj;}第14页,共19页,2023年,2月20日,星期四运算符重载C++语言的函数重载机制为程序员提供了很大的灵活性,在加上运算符重载机制后,在某些应用领域中我们就能够以一种更自然的方式来编写程序。在类中重载一个运算符实质上是在类中定义一个运算符函数,它与定义一个重载函数非常类似,只不过要用保留字operator加上运算符来命名函数。类成员运算符重载的一般形式为:类型类名::operator运算符(参数表){……//运算符函数体}其中,尽管运算符函数的返回值类型可以是任意的,但一般设计为当前的类类型以便进行复合运算,与重载函数不同,运算符函数的参数受到所重载的运算符的约束,不可随意指定。第15页,共19页,2023年,2月20日,星期四重载二元运算符作为一个类的成员函数时,只需要给它显式地传递一个参数,而另一个参数缺省地规定为this指针,且对应的实际参数必须是二元运算的左操作数;同样道理,一元运算符函数使用的参数是缺省规定的this指针。因而重载二元运算符要求一个参数,重载一元运算符不用参数。定义二元运算符函数时,在函数中使用this指针相当于使用左操作数,而右操作数是显式地传递的。函数体可以修改左操作数的值,但能否修改右操作数的值取决于右操作数的传递方式。如:

temp.real=real+other.real;相当于temp.real=this->real+other.real;这样就相当于使用了两个参数。在重载二元运算符时必须注意两个操作数的左右位置。对于加法而言,由于加法可交换,所以操作数的次序不会产生什么影响。但对于减法运算而言,左右操作数位置的不同将导致运算结果的不同,所以必须正确地认识两个参数的次序。在使用重载运算符时,采用运算符原来的表达式形式。如主函数中使用c1+c2,这时相当于调用运算符左边对象c1的运算符函数“+”,并通过this指针隐式地将c1传递给运算符函数“+”,运算符右边对象c2作为另一个参数传递给运算符函数“+”。第16页,共19页,2023年,2月20日,星期四友元运算符重载运算符重载中,二元运算符只带一个右参数,左操作数必须与定义的类类型相同,否则就会出错。如复数的实验中若最后的运算式改为c3=10-c1;则为非法的,因为“-”的左操作数必须是一个COMPLEX类型的对象。可以使用强制类型转换,如c3=COMPLEX(10)-c1;但,麻烦又不直观。另一种解决途径就是使用友元运算符重载来取代类成员运算符。友元运算符的参数规则与类成员运算符不同,一元运算符函数必须显式地声明一个参数,二元运算符函数必须显式声明两个参数,因为友元函数并不是一个类成员函数,在友元函数体中,不能使用this指针。当一个运算符的操作需要修改类对象状态时,应该以成员函数重载。例如,需要左值操作数的运算符(如=、*=、++等)应该用成员函数重载。如果以友元函数重载,可以使用引用参数修改

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论