下载

2下载券

加入VIP
  • 专属下载特权
  • 现金文档折扣购买
  • VIP免费专区
  • 千万文档免费下载

上传资料

关闭

关闭

关闭

封号提示

内容

首页 第九章 运算符重载

第九章 运算符重载.doc

第九章 运算符重载

浩哥
2018-09-09 0人阅读 举报 0 0 0 暂无简介

简介:本文档为《第九章 运算符重载doc》,可适用于IT/计算机领域

第九章:运算符重载第九章:运算符重载第九章:运算符重载学习目标·运算符重载的一般概念·双目运算符重载·类型转换运算符重载·流运算符重载·单目运算符重载·赋值运算符重载·下标运算符重载·Newdelete运算符重载·指针运算符重载运算符重载一般概念C内部定义的数据类型(int,float,…)的数据操作可以用运算符号来表示其使用形式是表达式用户自定义的类型的数据的操作则用函数表示其使用形式是函数调用。为了对用户自定义数据类型的数据的操作与内定义的数据类型的数据的操作形式一致C提供了运算符的重载通过把C中预定义的运算符重载为类的成员函数或者友员函数使得对用户的自定义数据类型的数据对象的操作形式与C内部定义的类型的数据一致。运算符重载即对C内部的基本数据类型所支持的运算符赋予新的含义让其可操作自定义的数据类型。运算符重载指对已知的运算符在新的场合通过程序实现新的行为。运算符重载规则·允许重载的运算符标准C中提供的运算符很多但允许重载的运算符只能是下表中所列。参见下表。(允许重载的运算符列表)双目运算符*关系运算符==!=<><=>=逻辑运算符||单目运算符*自增自减运算符位运算符|~^<<>>赋值运算符===*====|=^=<<=>>=空间申请和释放Newdeletenewdelete其他运算符()>>*,·不允许重载的运算符不允许重载的运算符只有个:·(成员访问符)·*(成员指针访问运算符)·::(域运算符)·Sizeof(长度运算符) typeoftypedef·:(条件运算符号)·其他规则)不允许自己定义新的运算符只能对已有的运算符号进行重载)重载不能改变运算符运算对象的个数如>和<是双目运算符重载后仍为双目运算符,需要两个参数)重载不能改变运算符的结合性如=是从右至左重载后仍然为从右至左)重载不能改变运算符的优先级别例如*优先于+,那么重载后也是同样的优先级别)重载运算符的函数不能有默认的参数否则就改变了运算符参数的个数与第条矛盾)重载的运算符必须和用户的自定义数据类型一起使用其参数至少应有一个是类对象(或者类对象的引用)或者说参数不能全部是C的标准类型)运算符重载函数可以是类的成员函数也可以是类的友员函数也可以使普通函数。运算符重载的三种方式对同一个运算符号往往可以通过普通函数、友员函数和类成员函数这三种方式实现重载以完成同样第功能。通过普通函数实现运算符重载的特点是自定义类即数据类型不得将其数据成员公开以便让普通函数可以访问类的数据成员这破坏了类的封装性所以这种重载方式要少用或不用C语言之所以提供此种方式是为了与C语言的兼容性。通过友员函数重载运算符的特点可以不破坏类的封装性类的数据成员可以是私有的但这种方式需要在类中定义友员函数以允许友员函数可以操作类的私有数据成员这在实际使用中也很不方便所以不在必要时不要使用这量的必要指的是C中有一些运算符不能用类成员函数的方式实现重载比如用于cout和cin的流提取符最好使用友员函数实现重载。通过类成员函数重载运算符是我们推鉴使用的运算符重载函数是类的成员函数正好满足了类的封装性要求。这种运算符重载的特点是类本身就是一个运算符参数参见实例。以下我们分别对一些主要的运算符重载的具体情况展开讨论。单目运算符、双目运算符重载我们知道单目运算符是指该运算符只操作一个数据由此产生另一个数据如正数运算符和负数运算符。双目运算符则是要操作两个数据由此产生另一个数据如一般的算术运算符和比较运算符都双目运算符。)在一个类中重载一元运算符为类运算符的形式为:返回值类型类名::operator(){…})在一个类中重载一元运算符为友员运算符的形式为:返回值类型operator(const类名obj){…})在一个类中重载二元运算符为类运算符的形式为:返回值类型类名::operator(参数){…}双目运算符需要两个运算符这量的函数原型为何仅列出了一个参数?这是因为类本身也是一个参数存取该参数可以通过this关键字来实现。)在一个类中重载二元运算符为友员运算符的形式为:返回值类型operator(参数参数){…}关于双目运算符的例子参见例。转换构造函数和类型转换运算符(函数)类的转换构造函数只带一个参数它把参数类型的数据转换成相应类型的类的对象。这与拷贝构造函数不同拷贝构造函数的参数是同类型对象的引用。例如classCComplex{public:doublemfRealdoublemfImagCComplex(){}CComplex(doublefReal,doublefImag=){mfReal=fRealmfImag=fImag}}intmain(){CComplexcpxcpx=return}“cpx=”一句将数值转换成一个临时的Ccomplex对象并拷贝给cpx。与之相对应可以通过转换运算符将一个类的对象转换成其他类型的数据。对于类X,转换后的类型为T,则类型转换运算符的形式为:X:operatorT(){…}如classCComplex{public:doublemfRealdoublemfImagCComplex(){}CComplex(doublefReal,doublefImag=){mfReal=fRealmfImag=fImag}operatordouble(){returnmfReal}}intmain(){CComplexcpx(,)doublef=double(cpx)return}类型转换运算符的特点是:)待转换类型是类自己)转换后的目标类型要和operator构成了运算符重载的函数名称即operatordouble)类型转换运算符没有返回类型但必须要写return语句返回目标类型的值。·下面是一个类型转换函数的例子将复数类的转换为一个字符串。operatorchar*(){char*s=(char*)malloc()sprintf(s,"Value:(f,f)",real,imag)returns}·同类型的转换构造函数和类型转换函数同时存在时会发生二义性例子:classComplex{public:Complex(){real=imag=}Complex(doubler,doublei){real=rimag=i}Complexoperator(constCComplexcpx)转换构造函数Complex(doublec){real=cimag=}类型转换函数operatordouble(){returnreal}public:doublerealdoubleimag}intmain(){Complexa,b,cc=abc=ac=areturn}其中语句ab不会发生二义性C编译器会定位到加的双目运算符函数并调用函数返回值赋给变量c。语句a和+a则要发生二义性因为编译器可以变量a调用转换构造函数产生一个double数据类型进行两个double数据类型的加法也可以将常数调用转换构造函数生成一个复数类型的变量调用两个复相加的运算符函数函数返回一个复数类变量并赋给变量c。流运算符的重载<<>>流运算符的最大特点是其第一个参数类型一定是类ostream或istream并且返回值也是类ostream或istream这样才能将自定义类的变量输出或输入到流中。显然流运算符不能通过类的成员函数重载因为通过类的成员函数重载必须是运算符的第一个参数是类自己。一般我们用友员来实现流运算符的重载不用普通函数来实现。参见例。前加和后加运算符的重载前加和后加运算符是一种单目运算符的重载正数和负数单目运算符的重载与此类似。其由于通过类的成员函数实现运算符的重载第一个参数一定得是类自己所以前加的函数原型中没的参数。如下:ComplexComplex::operator(){returnComplex(real,imag)}后加也是单目运算符C编译器如何区分后加运算符的情况呢?C编译器将后加运算符后加强行加一个参数类型int该参数的作用是仅仅区分之用。ComplexComplex::operator(int)after{Complextemp(*this)temprealtempimagreturntemp}赋值运算符的重载重载方法缺省的赋值运算符是实行对象间的按位拷贝如果类成员中含有指针类型的数据成员一般应该将该类的赋值运算符重载因为这时调用缺省的赋值运算符没有意义。如:classCMyString{private:char*mpszDatapublic:CMyString(char*pszData)构造函数CMyString(CMyStringobjStr)拷贝构造函数CMyStringoperator=(CMyStringobjStr)重载=操作符CMyStringoperator=(char*pszData)重载=操作符~CMyString(){deletempszData}}CMyString::CMyString(char*pszData){mpszData=newcharstrlen(pszData)strcpy(mpszData,pszData)}CMyString::CMyString(CMyStringobjStr){mpszData=newcharstrlen(objStrmpszData)strcpy(mpszData,objStrmpszData)}CMyStringCMyString::operator=(CMyStringobjStr){if(this==objStr)return*thisdeletempszDatampszData=newcharstrlen(objStrmpszData)strcpy(mpszData,objStrmpszData)return*this}CMyStringCMyString::operator=(char*pszData){deletempszDatampszData=newcharstrlen(pszData)strcpy(mpszData,pszData)return*this}intmain(){CMyStrings="abc"CMyStrings="xyz"s=""s=sreturn}赋值运算符和拷贝构造函数的区别和联系相同点:都是将一个对象COPY到另一个中去不同点:拷贝构造函数涉及到要新建立一个对象。在以下三种情况下要调用拷贝构造函数:)形如Aa=b由于对象a是新建的对象所以这种情况并不调用赋值运算符而是要调用拷贝构造函数)对象做函数参数时由于要在堆栈中建立新对象所以要调拷贝构造函数)对象是函数的返回值而赋值运算符不涉及到对象的建立。在一个对象中如果不定义赋值运算符和拷贝构造函数编译器将为之生成默认的赋值运算符和拷贝构造函数它只进行按位COPY来实现对象的COPY或赋值。一般来讲当一个类定义有指针默认的赋值运算符和拷贝构造函数将不能完成对象的赋值和COPY因为按位COPY的结果是将一对象的指针值COPY到另一个对象中这会引起相同的内存被多个对象引用的错误。这时要注意一个问题:引用或指针做函数参数或返回值时函数调用时会不会调用类的拷贝构造函数呢?是不会的因为函数在调用时不会涉及到新对象的建立仅仅是将原对象的地址进行了COPY。下标运算符重载标准情况下运算符用于访问数组的元素。我们可以通过重载下标运算符为类运算符。使得可以象访问数组元素一样的访问对象中的数据成员。C只允许把下标运算符重载为非静态的成员函数。下标运算符的定义形式为:TT::operator(T)其中T为希望返回的数据类型T为类名T为下标它可以是任意类型。如需访问第节中的CMyString的某个字符的话在类中可声明重载运算符:charoperator(intiIndex)在外部定义该运算符重载函数charCMyString::operator(intiIndex){if(iIndex<strlen(mpszData))returnmpszDataiIndexreturn}new和delete重载通过重载new和delete,我们可以自己实现内存的管理策略。new和delete只能重载为类的静态运算符。而且重载时无论是否显示制定static关键字编译器都认为时静态的运算符重载函数。·重载new时必须返回一个void*类型的指针它可以带多个参数但第一个参数必须是sizet类型该参数的值由系统确定。classCTest{void*operatornew(sizetnSize){cout<<"newcalled,size="<<nSizevoid*pRet=::newcharnSizereturnpRet}}·重载delete时必须返回void类型它可以带有多个参数但第一个参数必须时要释放的内存的地址其类型为void*,如果重载delete时指定了第二个参数第二个参数必须为sizet类型。接上:classCTest{void*operatornew(sizetnSize){cout<<"newcalled,size="<<nSizevoid*pRet=::newcharnSizereturnpRet}voidoperatordelete(void*pVoid){cout<<"deletecalled"<<endl::deletepVoid}}·一个类可以重载多个new运算符但是只能重载一个delete类运算符。指针运算符>的重载classCDataSetPtr{private:CDataSet*mpDataSetpublic:CDataSetPtr(){mpDataSet=newCDataSet}~CDataSetPtr(){deletempDataSet}CDataSet*operator>(){returnmpDataSet}}voidmain(){CDataSetPtrpDataSetintiValuepDataSet>GetField(iValue,"Title")}例通过复数类的相加运算符演示运算符重载的几种实现方式如有下列复数类:classCComplex{public:doublemfRealdoublemfImagcharmszStatusCComplex(){mfReal=mfImag=}CComplex(doublefReal,doublefImage){mfReal=fRealmfImag=fImage}}如果要实现将该类的两个对象的实部间相加、虚部间相加生成一个新的对象即:对象=对象+对象)如果用普通函数的实现运算符的重载可以用如下形式CComplexoperator(constCComplexcpx,constCComplexcpx){CComplexcpxRetcpxRetmfReal=cpxmfRealcpxmfRealcpxRetmfImag=cpxmfImagcpxmfImagreturncpxRet}通过普通函数重载来实现必须将复数类的私有数据成员公开这破坏了类的封装性)由于为类重载运算符函数往往要访问类的成员而类为了封装性的需要往往将成员声明为非公有的因此往往将一个运算符函数重载为类的成员函数或者友员函数。如果CComplex的mfReal,mfImag是类的私有或者保护成员那么普通函数无法访问在此情况下就不能实现上述重载功能此时可以通过友员函数的方式实现运算符+的重载。如下声明方式可以在类中声明友员函数上面的普通函数为友员函数classCComplex{private:doublemfRealdoublemfImagcharmszStatuspublic:CComplex(){mfReal=mfImag=}CComplex(doublefReal,doublefImage){mfReal=fRealmfImag=fImage}friendCComplexoperator(constCComplexcpx,constCComplexcpx)}函数的实现与普通函数相同。)也可以将运算符重载为类的成员函数如下:classCComplex{private:doublemfRealdoublemfImagcharmszStatuspublic:CComplex(){mfReal=mfImag=}CComplex(doublefReal,doublefImage){mfReal=fRealmfImag=fImage}CComplexoperator(constCComplexcpx)}并在类外定义该成员函数:CComplexCComplex::operator(constCComplexcpx){CComplexcpxRetcpxRetmfReal=this>mfRealcpxmfRealcpxRetmfImag=this>mfImagcpxmfImagreturncpxRet})运算符重载函数如何调用呢?可以象调用一般的函数一样调用运算符重载函数运算符重载函数的函数名称是operator例如:voidmain(){CComplexc,c,cC=coperator(c)}但这样书写很不直观也不方便。C编译提供了一种简写的方式如下:c=cc例通过友员函数来实现复数类的流运算符重载classComplexostreamoperator<<(ostreamos,Complexc)istreamoperator>>(istreamis,Complexc)Complexoperator(Complexfirst,Complexsecond)classComplex{public:Complex(){real=imag=}Complex(doubler,doublei){real=rimag=i}转换构造函数Complex(doublec){real=cimag=}类型转换函数operatordouble(){returnreal}operatorchar*(){char*s=(char*)malloc()sprintf(s,"Value:(f,f)",real,imag)returns}friendComplexoperator(Complexfirst,Complexsecond)friendostreamoperator<<(ostreamos,Complexc)friendistreamoperator>>(istreamis,Complexc)voiddisplay(){cout<<"("<<real<<","<<imag<<"i)"<<endl}private:doublerealdoubleimag}Complexoperator(Complexfirst,Complexsecond){returnComplex(firstrealsecondreal,firstimagsecondimag)}istreamoperator>>(istreamis,Complexc){cout<<"inputacomplex:n"<<endlis>>creal>>cimagreturnis}ostreamoperator<<(ostreamos,Complexc){os<<"complexvalueis:("<<creal<<","<<cimag<<")"<<endlreturnos}intmain(){Complexcc,ccccin>>cc>>ccccout<<cc<<ccc<<endlreturn}·上述实例中第一条语句含义如何理解?它是类的前导声明目的是让编译器知道标识符Complex是一个类以便将友员函数编译通过。·语句cout<<cc<<ccc<<endl如何理解?cout是ostream类的全局变量cout<<cc将导致友员函数的调用并返回一个临时的ostream对象相当于operator<<(cout,cc)。所以该语句相当于下述伪码:operator<<((operator<<((operator<<(cout,cc)),ccc)),endl)·语句cin>>cc>>ccc的理解与输出的流提取符的理解是完全一样的内容总结运算符重载的基本概念算符重载是有规则的不能够自随意定义一个特殊的符号对其进行重载。必须是对C语言已有的运算符进行重载而且还不是全部的预定义运算符都可以重载。在重载运算符时不能改变该运算符原有的操作数的个数及其运算的结合性和优先级。在重载运算符时运算符函数不能定义默认参数而且函数的参数要至少有一个自定义的数据类型有三种运算符重载的实现方案:普通函数、友员函数和类成员函数。我们要学会常用的单目和双目运算符重载流运算符的重载赋值和下标运算符的重载。new和delete运算符及指针运算符在实际使用中较少。独立实践实践开发一个显示时间的程序要求时间类可以进行加减前加和后加并且可以通过下标运算符来取出时间的时分秒。实践在上述时间类显示程序上加一个功能通过流运算符可以从键盘上输入时间并显示之。实践思考:什么时候必须要定义自己的拷贝构造函数和赋值函数?实践编写函数实现两个静态整型数据的相加运算并且是指能够使用流运算符进行输入输出。实践编写函数实现两个动态整型数据的相加运算并且是指能够使用流运算符进行输入输出。注意类的成员变量就是一个整型变量的指针。classCMatrix{protected:int*mpi…}

用户评价(0)

关闭

新课改视野下建构高中语文教学实验成果报告(32KB)

抱歉,积分不足下载失败,请稍后再试!

提示

试读已结束,如需要继续阅读或者下载,敬请购买!

评分:

/25

VIP

在线
客服

免费
邮箱

爱问共享资料服务号

扫描关注领取更多福利