资源描述
2第四章 运算符重载 4.1什么是运算符重载4.2运算符重载的方法4.3重载运算符的规则4.4运算符重载函数作为类成员函数和友元函数4.5重载双目运算符4.6重载单目运算符4.7重载流插入运算符和流提取运算符 4.8 类和其他数据类型的转换34.1什么是运算符重载C+ 为程序员提供了灵活的手段,让程序员自己定义类,自己设计相应的运算符(必须在已有的运算符基础上设计),使之应用于自己定义的类。与函数重载类似,对已有的运算符赋予新的含义,用一个运算符表示不同功能的运用一个运算符表示不同功能的运算,这就是运算符重载算,这就是运算符重载。实际上,我们在此之前已经使用了运算符重载。如是C+的移位运算符,它又与流对象cout配合作为流插入运算符,这是C+对进行了重载处理。4运算符重载的实质n运算符重载是对已有的运算符赋予多重含义。n必要性:C+中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)n实现机制q将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。q编译系统对重载运算符的选择,遵循函数重载的选择原则。5例4.1 通过成员函数实现复数的加法。class Complex private: double real; double imag; public: Complex() real=0;imag=0; Complex(double r,double i) real=r; imag=i; Complex complex_add(Complex &c2); void display(); ;6Complex Complex:complex_add(Complex &c2) Complex c;c.real=real +c2.real; c.imag=imag+c2.image; return c; void Complex:display() cout(real,imagi)endl; int main() Complex c1(3,4),c2(5,-10); Complex c3; c3=plex_add(c2); coutc1=; c1.display(); coutc2=; c2.display(); coutreal +c2.real; c.imag = this-imag+c2.image;在main函数中通过对象c1调用加法函数,上面的语句相当于: c.real = c1.real +c2.real; c.imag = c1.imag+c2.image;能否用+运算符实现复数加法?84.2运算符重载的方法运算符重载的方法是定义一个重载运算符函数,在需要时系统自动调用该函数,完成相应的运算。运算符重载实质上是函数的重载。运算符重载函数的格式是:数据类型数据类型 operator 运算符运算符(形参表形参表) 重载处理重载处理 数据类型:是重载函数值的数据类型。数据类型:是重载函数值的数据类型。operator 是保留字是保留字9规则和限制nC+中可以重载除下列运算符外的所有运算符:. .* : ?:n只能重载C+语言中已有的运算符,不可臆造新的。n不改变原运算符的优先级和结合性。n不能改变操作数个数。n经重载的运算符,其操作数中至少应该有一个是的数据类型是类。10两种形式n重载为类成员函数。n重载为友元函数。11运算符函数n声明形式函数类型 operator 运算符(形参) .n重载为类成员函数时,类本身是一个操作操作数,如果需要另一个操作数由函数的参数提供n重载为友元函数时 参数个数=原操作数个数,且至少应该有一个自定义的形参。12不能重载的运算符只有5个:. 成员运算符.* 成员指针运算符: 域运算符sizeof 长度运算符?: 条件运算符重载函数名是由operator和运算符联合组成。复数加法运算符重载函数原型可以是: Complex operator+ (Complex &c2);例4.2 重载运算符+,用于两个复数相加。13分析: 定义一个复数类,用成员函数实现加号的重载函数。两个复数相加结果仍是复数,所以函数的返回值的类型也是复数类。用成员函数实现运算符重载函数时,调用格式是“对象名对象名.成成员名员名”,此时对象就是一个参与运算的操作数,加法还需要另一个操作数,这个操作数用函数的参数传递,参数的类型就是复数类。而运算结果用函数值返回。14class Complex public: Complex() real=0; imag=0; Complex(double r,double i)real=r;imag=i; Complex operator + (Complex &c2); void display(); private: double real; double imag; ;15Complex Complex:operator + (Complex &c2) Complex c; c.real=real+c2.real; c.imag=imag+c2.imag;return c; void Complex:display()cout(real,imagi)endl; 16int main()Complex c1(3,4),c2(5,-10),c3; c3=c1+c2; coutc1=;c1.display(); coutc2=;c2.display(); coutc1+c2=;c3.display(); return 0;17说明:(1)用运行符重载函数取代了例 4.1中的加法成员函数,从外观上看函数体和函数返回值都是相同的。(2)在主函数中的表达式c3=c2+c1 取代了例4.1中的c3=plex_add(c2) ,编译系统将表达式c3=c1+c2 解释为 c1.operator + ( c2 )对象c1调用的重载函数operator + ,以c2为实参计算两个复数之和。18请考虑在例4.2中能否用一个常量和一个复数相加?如 c3 = 3 + c2; / 错误错误应该定义对象:Complex C1(3.0,0) : c3 = C1 + c2;注意:运算符重载后,其原来的功能仍然保留,编译系统根据运算表达式的上下文决定是否调用运算符重载函数。 运算符重载和类结合起来,可以在C+ 中定义使用方便的新数据类型。194.3重载运算符的规则(1)C+只允许已有的部分运算符实施重载。(2)不能重载的运算符有五个。(3)重载不改变操作数的个数。(4)重载不改变运算符的优先级。(5)运算符重载函数不能带默认值参数。(6)运算符重载函数必须与自定义类型的对象联合使用,其参数至少有一个类对象或类对象引用。(7)C+默认提供 = 和 & 运算符重载。20(8)运算符重载函数可以是类成员函数也可以是类的友元函数,还可以是普通函数。(9)C+规定赋值运算符、下标运算符、函数调用运算符必须定义为类的成员函数;而输出流插入、输入流提取、类型转换运算符不能定义为类的成员函数。214.4运算符重载函数作为类成员函数和友元函数在例4.2程序中对运算符+进行了重载,该例将运算符重载函数定义为复数类的成员函数。从该程序中看到运算符重载为成员函数时,带一个类类型的形参,而另一个加数就是对象自己。例4.3 将加法运算符重载为适用于复数加法,重载函数作为类的友元函数。22#include class Complex public: Complex () real=0;imag=0; Complex (double r) real=r;imag=0; Complex (double r,double i) real=r;imag=i; friend Complex operator+ (Complex &c1, Complex &c2); void display(); private: double real; double imag; ;23Complex operator+ (Complex &c1, Complex &c2) return Complex(c1.real+c2.real, c1.imag+c2.imag) ; / 将调用构造函数将调用构造函数 void Complex:display()cout(real,imagi)endl;24int main() Complex c1(3,4),c2(5,-10),c3; c3=c1+c2; coutc1=; c1.display(); coutc2=; c2.display(); coutc1+c2=; c3.display(); return 0;25加法运算符重载为友元函数,C+ 在编译时将表达式c1+c2解释为operator + ( c1, c2)即相当于执行以下函数Complex operator + ( Complex & c1, Complex & c2 ) return Complex( c1.real + c2.real , c1.imag + c2.imag ) ; 因为普通函数是不能直接访问对象的私有成员,如果普通函数必须访问对象的私有成员,可调用类的公有成员函数访问对象的私有成员。这会降低效率。26n使用成员函数重载运算符要求左操作数必须是该类对象如想将一个复数和一个整数相加,运算符重载函数作为成员函数定义如下:Complex Complex :operator + ( int & i ) return Complex( real + i , imag ) ; 注意在运算符+的左侧必须是Complex类对象,程序中可以写成: c3 = c2 + n不能写成:c3 = n + c227n如果要求在使用重载运算符时,运算符左侧操作数不是对象,就需要将运算符重载函数定义为友元函数:friend Complex operator + ( int & i , Complex & c ) return Complex( c.real + i , c.imag ) ; 友元函数不要求第一个参数必须是类类型,但是要求实参要与形参一一对应:c3 = n + c2 / 顺序正确顺序正确c3 = c2 +n / 顺序错误顺序错误28n由于使用友元会破坏类的封装,要尽量将运算符重载函数定义为成员函数。n除非有特殊需要,才使用友元函数重载运算符。294.5重载双目运算符n双目的意思是运算符左边和右边的操作数均参加运算。n如果要重载 B 为类的成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1 为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。n经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)。30n例4.4 定义一个字符串类String,用来处理不定长的字符串,重载相等、大于、小于关系运算符,用于两个字符串的等于、大于、小于的比较运算。n操作数:两个操作数都是字符串类的对象。n规则:两个字符串进行比较。n将“”运算重载为字符串类的成员函数。31(1)先建立一个String类#include #include class String / String 是用户自己指定的类名是用户自己指定的类名 public: String() p=NULL; String( char *str ) ; void display(); private: char *p; ;32String:String(char *str) p=str; void String:display() coutp;int main() String string1(Hello),string2(Book); string1.display(); coutendl; string2.display(); return 0;33先编写出简单的程序框架,编写和调试都比较方便。构造函数是把定义对象时的实参的地址赋予数据成员p,p是指向实参的指针。程序实现了建立对象、输出字符串对象的功能。程序运行结果分别输出HelloBook(2)有了这个基础后,再增加所需的其他内容,先重载大于运算符。程序如下:34#include #include class String public: String () p=NULL; String (char *str); friend bool operator(String &string1,String &string2); private: char *p; ;String:String(char *str) p=str; 35void String:display() cout(String &string1,String &string2) if(strcmp(string1.p,string2.p)0) return true; else return false; int main() String string1(Hello),string2(Book); coutstring2)endl; return 0;36运算符重载函数定义为友元函数,函数值是布尔类型,在函数中调用了strcmp库函数,string1.p指向“Hello”,string2.p指向“Book”,程序运行结果是1。(3)扩展到对另外三个运算符重载在String类体中声明三个重载函数是友元函数,并编写相应的函数。37#include #include class String public: String()p=NULL; String(char *str); friend bool operator (String &string1,String &string2); friend bool operator (String &string1,String &string2); friend bool operator=(String &string1,String &string2); void display(); private: char *p; ;38String:String(char *str) p=str;void String:display()cout(String &string1,String &string2) if(strcmp(string1.p,string2.p)0) return true; else return false; 39bool operator(String &string1,String &string2) if(strcmp(string1.p,string2.p)0) return true; else return false; bool operator=(String &string1,String &string2) if(strcmp(string1.p,string2.p)=0) return true; else return false; 40int main() String string1(Hello),string2(Book),string3(Computer); coutstring2)endl; cout(string1string3)endl; cout(string1=string2)endl; return 0;运行结果为100414.6重载单目运算符单目运行符只要一个操作数,由于只有一个操作数,重载函数最多只有一个参数,如果将运算符重载函数定义为成员函数还可以不用参数。下面以自增运算符+为例,学习单目运算符的重载函数的编写方法。例4.5 有一个Time类,数据成员有时、分、秒。要求模拟秒表,每次走一秒,满60秒进位,秒又从零开始计数。满60分进位,分又从零开始计数。输出时、分和秒的值。42#include using namespace std;class Time public: Time() hour=0;minute=0;sec=0; Time(int h,int m,int s):hour(h),minute(m),sec(s) Time operator+(); void display() couthour:minute:sec=60) sec=sec-60; minute+; if(minute=60) minute=minute-60; hour+; hour=hour%24; return *this;4344 int main() Time time1(23,59,0); for (int i=0;i61;i+) + time1; time1.display(); return 0;45nC+中除了有前+外,还有后+。同样的运算符由于操作数的位置不同,含义也不同。 Time operator+(); 形式的运算符重载,既可以对应前缀,也对应后缀。n怎样区分前+和后+? C+给了一个方法,在自增或自减运算符重载函数中,增加一个int 形参,此时成员函数表示后缀对应的函数。例4.6 在例4.5 的基础上增加后+运算符重载函数。46#include using namespace std;class Time public: Time()hour=0;minute=0;sec=0; Time(int h, int m,int s):hour(h),minute(m),sec(s) Time operator+(); Time operator+(int);void display()couthour:minute:sec=60) sec=sec-60; minute+; if(minute=60) minute=minute-60; hour+; hour=hour%24; return *this;4748分析:后+运算的含义是操作数先参加其他运算后再自加。如m=n+先将n的值赋予m,然后n再自加1。设计后+重载函数要遵循这个特性。Time Time:operator+(int) Time temp(*this); / 保存修改前的对象做返回值 +(*this); return temp;49int main() Time time1(21,34,59),time2; cout time1 : ; time1.display(); +time1; cout+time1: ; time1.display(); time2 = time1+; couttime1+: ; time1.display(); cout和移位运算符进行了重载,使它们分别成为流提取运算符和流插入运算符。用来输入或输出C+的标准类型数据,所以要用#include using namespace std;把头文件包含到程序中。n用户自定义类型的数据不能直接用输出和输入,如想用它们进行输入或输出,程序员必须对它们重载。51重载函数原型的格式如下:istream & operator (istream&,自定义类&);ostream & operator 重载函数和重载函数只能定义为友元函数,不能定义为成员函数,因为函数有两个形参,并且第一个形参不是自定义类型。524.7.1重载流插入运算符“” 例4.7 在例4.2的基础上用重载函数输出复数。分析:在类中声明重载函数是友元函数friend ostream& operator (ostream&,Complex&);在类外定义友元函数:ostream& operator (ostream& output,Complex& c) output(c.real+c.imagi)endl; return output;53#include int main()Complex c1(2,4),c2(6,10),c3; c3=c1+c2; coutc3; return 0;54分析C+怎样处理”cout c3;” 运算符的左边是ostream的对象cout,右边是程序员自定义类complex的对象c3,语句符合运算符重载友元函数operator的形参类型要求,系统调用友元函数,C+把这个语句解释为:operator ( cout , c3);通过形参引用传递,函数中的output就是cout,函数中的c就是c3,函数就变成: cout(c3.real+c3.imagi)endl; return cout;55return cout 是将输出流现状返回。C+ 规定运算符运算符”例4.8 在例4.7 的基础上增加流提取运算符重载函数,用cin输入复数,用cout (istream&,Complex&);在类外定义函数:istream& operator (istream& input,Complex& c)cout c.real c.imag; return input;57int main() Complex c1,c2; cinc1c2; coutc1=c1endl; coutc2=c2重载函数中的形参input是istream类对象引用,在执行cinc1时,调用operator函数,将cin引用传递给input,input是cin的别名,同样c是c1的别名。因此,input c.real c.imag;相当于cin c1.real c1.imag。函数返回cin的新值。使程序可以用重载函数连续从输入流提取数据给complex类对象。58程序逻辑上是正确的,但还有缺陷,如果输入的虚部是负数时,输出的形式变成:c2 = ( 4+ - 10i )在负数前多个正号。可以对程序稍做修改: ostream& operator (ostream& output,Complex& c) output(= 0 ) output + ; output c.imagi)endl; return output;59类型转换运算符的重载q类型可以当成运算符(强制类型转换)q内置类型转换为类(例Common_to_class)n类有类型转换的功能,能将其数据类型转换为本类对象,并且能够隐式进行n类型转换的前提是必须提供形式一致的构造函数n在内置类型作为实参时,如不和形参(类)一致,进行隐式的转换5960n类转换为其他数据类型Class_Name:operator type( ) return (type类型的实例); 类型转换函数没有参数,没有返回类型,但这个函数体内必须有一条返回语句,返回一个type类型的实例。6061q类转换为其他类型能隐式进行(见例class_to_common)61class INTEGER/转换为整数int num;public:INTEGER(int anint=0)num=anint;operator int( )return num;62n编译器在进行类型转换时,总试图使用用户定义的类型转换函数进行类型转换,如果这样不能成功,则使用标准的类型转换机制。如果都不成功,则转换失败。n在类型转换具有二义性的情况下,必须使用显式类型转换。即 (type) obj 或 type (obj)将对象obj转换为具有type类型的一个对象。(见例ambiguity)6263n重载赋值运算符“=”n对类(对象)能够使用赋值运算符“=”,执行位拷贝的功能n赋值运算符“=”可以被重载,用户可以定义自己需要的重载“=”的运算符重载函数。重载了的运算符函数operator=不能被继承,而且它必须被重载为成员函数,一般重载格式为: X& X:operator=(const X & from) /复制复制X的成员的成员 n拷贝构造函数和赋值运算符都是把一个对象的数据成员拷贝到另一对象。它们的区别是,拷贝构造函数要创建一个新对象,而赋值运算符则是改变一个已存在的对象的值。(例override_assigner)63
展开阅读全文