C++运算符重载知识总结5.1编译时的多态性与运行时的多态性在C++中,多态性的实现和联编(也叫绑定)这一概念有关。源程序—编译、连接—可执行文件这一过程时把可执行代码联编在一起的过程。其中在运行之前就完成的联编叫做静态联编,又叫前期联编;而在程序运行时才完成的联编叫动态联编,也叫后期联编。静态联编(前期联编):指系统在编译时就决定如何实现某一动作。要求在编译时就知道调用函数的全部信息。静态联编支持的多态性成为编译时多态性(静态多态性),通过函数重载(包括运算符重载)和模板实现。优点:函数调用速度很快,效率高。动态联编(后期联编):指系统在运行时动态实现某一动作。采用这种联编方式,一直要到程序运行时才能确定调用哪个函数。动态联编支持的多态性成为运行时多态性(动态多态性),通过虚函数实现。优点:提供了更好的灵活性、问题抽象性和程序易维护性。5.2运算符重载5.2.1在类外定义的运算符重载函数为了表达上的方便,人们希望预定义的运算符(如+、-、*、/等)在特定的对象上以新的含义进行解释,如希望能够实现两个复数类的加减,这就需要通过运算符重载来解决。C++中进行运算符重载时,必须定义一个运算符重载函数,其名字为operator,后随一个要重载的运算符。函数功能operator+加法operator-减法operator*乘法operator小于………...例如,使两个Complex类对象相加的例子#includeiostreamusingnamespacestd;classComplex{public:doublereal;doubleimag;Complex(doubler=0,doublei=0){real=r;imag=i;}};Complexoperator+(Complexco1,Complexco2)//类外定义运算符+的重载函数{Complextemp;temp.real=co1.real+co2.real;temp.imag=co1.imag+co2.imag;returntemp;}intmain(){Complexcom1(1.1,2.2),com2(3.3,4.4),total1,total2;total1=operator+(com1,com2);//运算符重载函数第一种调用方式,显式调用coutreal1=total1.real,imag1=total1.imagendl;total2=com1+com2;//运算符重载函数第二种调用方式,隐式调用coutreal2=total2.real,imag2=total2.imagendl;return0;}C++语言对运算符重载制定了以下一些规则:1.绝大部分运算符可以重载,不能重载的只有以下几个:.成员访问运算符.*成员指针访问运算符::作用域运算符sizeof长度运算符?:条件运算符2.只能对已有运算符进行重载,不允许用户自己定义新的运算符。3.运算符重载是针对新类型数据的实际需要,不建议改变原运算符的含义。(例如将+运算符重载为进行减法运算)4.运算符重载不能改变运算符的操作对象(即操作数)的个数。5.运算符重载不能改变运算符原有的优先级。6.运算符重载不能改变运算符原有的结合性。7.运算符重载函数的参数至少有一个是类对象(或其引用)。目的是防止用户修改用于标准类型数据的运算符性质。8.运算符重载函数可以是普通函数、类成员函数、类的友元函数。9.赋值运算符“=可以不必用户进行重载。5.2.2友元运算符重载函数1.定义友元运算符重载的语法形式1)在类的内部,形式为:friend函数类型operator运算符(形参表){函数体}2)类内声明,类外定义,形式为:classX{friend函数类型operator运算符(形参表);};函数类型operator运算符(形参表){函数体}2.重载为友元函数时,没有隐含的参数this指针,这样对于双目运算符,友元函数有两个参数,对于单目运算符,友元函数有一个参数。3.以下运算符不能定义为友元运算符重载函数:赋值运算符“=”、下标运算符“[]”、函数调用运算符“()”、“-”。5.2.3成员运算符重载函数1.定义成员运算符重载的语法形式1)在类的内部,形式为:函数类型operator运算符(形参表){函数体}2)类内声明,类外定义,形式为:classX{函数类型operator运算符(形参表);};函数类型X::operator运算符(形参表){函数体}2.对双目运算符而言,成员运算符重载函数的形参表中仅有一个参数,它作为运算符的右操作数,另一个操作数(左操作数)是隐含的,是该类的当前对象,它是通过this指针隐含地传递给函数的。3.对于单目运算符而言,成员运算符重载函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。5.2.4成员运算符重载函数与友元运算符重载函数的比较(1)对双目运算符而言,成员运算符重载函数参数表中含有一个参数,而友元运算符重载函数参数表中含有两个参数;(2)对于单目运算符而言,成员运算符重载函数的参数表中没有参数,而友元运算符重载函数参数表中含有一个参数。(3)双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但以下情况必须使用友元函数:将一个复数和一个整数相加Complexoperator+(inta){return(real+a,imag);}若com和com1是类Complex的对象,则以下语句是对的:com1=com+100;//正确,+左侧是类对象这条语句被解释为:com1=com.operator+(100);然而以下语句就不起作用了:com1=100+com;//错误,+左侧是整数这条语句被解释为:com1=100.operator+(com);若用友元函数则不会出现这种情况。c+100成员函数:c.operator+(100)友元函数:operator(c,100)100+c成员函数:100.operator+(c)友元函数:operator+(100,c)(4)友元运算符函数和成员运算符函数的调用形式:表达式友元函数调用成员函数调用a+boperator+(a,b)a.operator+(b)++aoperator++(a)a.operator++()-aoperator-(a)a.operator-()(5)一般来说,单目运算符最好被重载为成员函数,双目运算符被重载为友元函数。运算符“=、()、[]、-”只能作为成员函数;运算符“+=、-=、/=、*=、&=、!=、%=”建议重载为成员函数。5.2.5“++”和“--”重载增1减1运算符是单目运算符,它们又有前缀运算符和后缀运算符两种。为了区分这两种运算,将后缀运算视为双目运算符,在参数表中插入int。运算符名成员函数形式调用形式等价调用前缀++Xoperator++()++aa.Operator++()后缀++Xoperator++(int)a++a.Operator++(0)前缀--Xoperator--()--aa.Operator--()后缀--Xoperator--(int)a--a.Operator--(0)例题:使用成员函数重载运算符“——”#includeiostreamusingnamespacestd;classThree{public:Three(intI1=0,intI2=0,intI3=0);voidprint();Threeoperator--();//前缀方式Threeoperator--(int);//后缀方式private:inti1,i2,i3;};Three::Three(intI1,intI2,intI3){i1=I1;i2=I2;i3=I3;}voidThree::print(){couti1=i1,i2=i2,i3=i3endl;}ThreeThree::operator--(){--i1;--i2;--i3;return*this;//返回自减后的当前对象}ThreeThree::operator--(int){Threetemp(*this);i1--;i2--;i3--;returntemp;//返回自减前的当前对象}intmain(){Threeobj1(4,5,6),obj2,obj3(11,12,13),obj4;//隐式调用obj1.print();--obj1;obj1.print();obj2=obj1--;obj2.print();obj1.print();coutendl;//显式调用obj3.print();obj3.operator--();obj3.print();obj4=obj3.operator--(0);obj4.print();obj3.print();return0;}例题:使用友员函数重载运算符“十十”#includeiostreamusingnamespacestd;classThree{public:Three(intI1=0,intI2=0,intI3=0);voidprint();friendThreeoperator++(Three&);friendThreeoperator++(Three&,int);private:inti1,i2,i3;};Three::Three(intI1,intI2,intI3){i1=I1;i2=I2;i3=I3;}voidThree::print(){couti1=i1,i2=i2,i3=i3endl;}Threeoperator++(Three&op){++op.i3;++op.i2;++op.i1;returnop;//}Threeoperator++(Three&op,int){op.i3++;op.i2++;op.i1++;returnop;//返回自减前的当前对象}intmain(){Threeobj1(4,5,6),obj2(14,15,16);//隐式调用obj1.print();//显示原值++obj1;obj1.print();obj1++;obj1.print();//显式调用coutendl;obj2.print();//显示原值operator++(obj2);obj2.print();operator++(obj2,0);//后缀方式obj2.print();return0;}5.2.6赋值运算符“=”重载对于任意一个类,如果用户没有定义赋值运算符函数,那么系统将自动地为其生成一个默认的赋值运算符函数,例如:X&X::operator=(constX&source){//成员间赋值}采用默认的赋值运算符函数实现的数据成员逐一赋值的方法是一种浅层复制方法,通常,默认的赋值运算符函数是能够胜任工作的,但有些特殊情况下,如类中有指针类型时,使用缺省的赋值运算符函数会产生错误。1.指针悬挂问题#includeiostreamusingnamespacestd;classString{public:String(char*s){coutConstructorcalledendl;ptr=newchar[strlen(s)+1];strcpy(ptr,s);}~String(){coutDestructorcalled.---ptrendl;deleteptr;}private:char*ptr;};intmain(){Stringp1(book);Stringp2(jeep);p2=p1;return0;}运行结果如下:ConstructorcalledConstructorcalledDestructorcalled.---bookDestructorcalled.—葺葺葺葺这是由于默认的赋值运算函数是浅层复制,只是改变指针指向,并没有另开辟一块新的内存空间,使得第二次执行析构函数时企图释放同一空