C++程序设计课程介绍第11章 运算符重载.ppt
第11章 运算符重载,什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符运算符重载实例,什么是运算符重载,使系统内置的运算符可以用于类类型例如:+运算符能够实现2个对象间的加。例如:类A的对象a1、a2、a3,希望:a3=a1+a2;即:分别把对象a1和a2的各个数据成员值对应相加,然后赋给对象a3。,问题的提出,把某些事交给系统去做,用户只要知道相加就可扩充运算符的功能增强了C+语言的可扩充性使用户定义的类更像系统的内置类型,运算符重载的限制,不是所有的运算符都能重载重载不能改变运算符的优先级和结合性重载不能改变运算符的操作数个数不能创建新的运算符,可以重载的运算符,+-*/%&|!=+=-=*=/=%=&=|=&|+-*,-()newdelete new delete,不能重载的运算符,.*:?:sizeof,第11章 运算符重载,什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符运算符重载实例,运算符重载的方法,运算符重载就是写一个函数解释某个运算符在某个类中的含义要使得系统能自动找到重载的这个函数,函数名必须要体现出和某个被重载的运算符的联系。C+中规定,重载函数名为 operator 其中,为要重载的运算符。如要重载“+”运算符,该重载函数名为operator+。要重载赋值运算符,函数名为operator=。,函数原型,运算符的重载不能改变运算符的运算对象数。因此,重载函数的形式参数个数(包括成员函数的隐式指针this)与运算符的运算对象数相同 运算符重载可以重载成成员函数也可以重载成全局函数实现。重载成全局函数时,最好把此函数设为友员函数如果作为类的成员函数,它的形式参数个数比运算符的运算对象数少1。这是因为成员函数有一个隐含的参数this。在C+中,把隐含参数this作为运算符的第一个参数。当把一个一元运算符重载成成员函数时,该函数没有形式参数。把一个二元运算符重载成成员函数时,该函数只有一个形式参数,就是右操作数,当前对象是左操作数。,重载实例,为rational类增加“+”和“*”以及比较的重载函数,用以替换现有的add和multi函数,方案一:重载成成员函数,class Rational private:int num;int den;void ReductFraction();public:Rational(int n=0,int d=1)num=n;den=d;Rational operator+(const Rational,函数实现,Rational Rational:operator+(const Rational,bool Rational:operator(const Rational,方案二:重载成友员函数,class Rational friend Rational operator+(const Rational,函数的实现,Rational operator+(const Rational 其他函数实现略,重载后有理数类的使用,int main()Rational r1(1,6),r2(1,6),r3;r3=r1+r2;r1.display();cout+;r2.display();cout=;r3.display();cout endl;r3=r1*r2;r1.display();cout*;r2.display();cout=;r3.display();cout endl;return 0;,全局函数 vs成员函数,大多数运算符都可以重载成成员函数或全局函数。赋值(=)、下标()函数调用()和成员访问(-)必须重载成成员函数。具有赋值意义的运算符,如复合的赋值运算符以及+和-,不一定非要定义为成员函数,但最好定义为成员函数。具有两个运算对象的运算符最好重载为全局函数,这样可以使得应用更加灵活。如果把加运算定义成全局函数,r是有理数类的对象,则2+r是一个合法的表达式。,第11章 运算符重载,什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符运算符重载实例,几个特殊的运算符的重载,赋值运算符 下标运算符+和运算符的重载 重载函数的原型设计考虑输入输出运算符重载,赋值运算符,对任一类,如果用户没有自定义赋值运算符函数,那么系统为其生成一个缺省的赋值运算符函数,在对应的数据成员间赋值。一般情况下,这个缺省的赋值运算符重载函数能满足用户的需求。但是,当类含有类型为指针的数据成员时,可能会带来一些麻烦。,对IntArray类对象执行array1=array2的问题,会引起内存泄漏使这两个数组的元素存放于同一块空间中当这两个对象析构时,先析构的对象会释放存储数组元素的空间。而当后一个对象析构时,无法释放存放数组元素的空间,赋值运算符“=”的原型,赋值运算符只能重载成成员函数函数原型:X&X:operator=(const X&source)/赋值过程 一旦创建了对象x1,x2,可以用 x1=x2赋值。,IntArray类的赋值运算符重载函数,IntArray,赋值运算符重载要点,一般来讲,需要自定义拷贝构造函数的类也需要自定义赋值运算符重载函数。在赋值运算符重载函数中,已经将参数的值赋值给了当前对象,那为什么还需要返回值呢?记住,在C+中,赋值是一个运算,它可以形成一个表达式,而该表达式的结果值就是赋给左边的对象的值。因此,赋值运算符重载函数必须返回赋给左边的对象值。,赋值运算符重载和拷贝构造函数,一般来讲,需要拷贝构造函数的类也需要重载赋值运算符定义对象时给对象赋初值调用的是拷贝构造函数程序的语句部分中的赋值语句调用的是赋值运算符重载函数,几个特殊的运算符的重载,赋值运算符 下标运算符+和运算符的重载 重载函数的原型设计考虑输入输出运算符重载,下标运算符重载,能否象普通的数组那样通过下标运算操作IntArray类的对象,这样可以使IntArray类更像一个功能内置的数组。可以通过重载下标运算符()来实现 下标运算符是二元运算符,第一个运算数是数组名,第二个运算数是下标值下标运算符必须重载成成员函数,IntArray类的 重载,int,IntArray类的使用,定义:IntArray array(20,30);数组输入:for(i=20;i arrayi;数组输出:for(i=20;i=30;+i)cout arrayi t;,几个特殊的运算符的重载,赋值运算符 下标运算符+和运算符的重载 重载函数的原型设计考虑输入输出运算符重载,“+”和“-”重载,、-:是一元操作符这两个操作符可以是前缀,也可以是后缀。而且前缀和后缀的含义是有区别的。所以,必须有两个重载函数。问题:两个重载函数有相同的原型区分方法:前缀:一元操作符。后缀:二元操作符。,“+”和“-”重载 cont.,成员函数重载+ob重载为:ob.operator+()ob-重载为:ob.operator-(int)友元函数重载+ob重载为:operator+(X&ob)ob-重载为:operator-(X&ob,int)调用时,参数int一般传递给值0。,+、-重载实例,设计一个会报警的计数器类。该计数器从0开始计数,当到达预先设定好的报警值时,计数器会发出报警消息,计数器的值不再增加。,类定义,class Counter int value;/计数器的值int alarm;/报警值public:Counter(int a)value=0;alarm=a;Counter,类实现,Counter/返回修改前的状态,类的使用,int main()Counter cnt(3);/定义一个Counter类的对象,报警值为3 cnt.print();/显示对象的当前值,此时输出为0+cnt;cnt.print();/此时输出为1(+cnt).print();/调用前缀的+,输出2(cnt+).print();/调用后缀的+,当前对象的value已经/加1,报警。但输出的是2 cnt.print();/输出值为3 return 0;,几个特殊的运算符的重载,赋值运算符 下标运算符+和运算符的重载 重载函数的原型设计考虑输入输出运算符重载,重载函数的原型设计考虑,参数设计对于任何函数的参数,如果仅需要从参数中读,而不改变它,一般用const引用来传递。只有会修改左值参数的运算符,如赋值运算符,左值参数不是常量,所以用地址传递返回值的类型设计 运算符的结果产生一个新值,就需要产生一个作为返回值的新对象 对于逻辑运算符,人们希望至少得到一个int或bool的返回值 所有的赋值运算符(如,=,+=等)均改变左值,应该能够返回一个刚刚改变了的左值的非常量引用,值返回时的优化,在返回一个对象时,通常有两种写法。如某函数返回一个Rational类的对象,它的值为两个参数的成员对应相加。它的两种写法为return Rational(left.num+right.num,left.den+right.den);Rational tmp;tmp.num=left.num+right.num;tmp.den=left.den+right.den;return tmp;,两种写法的比较,前者的意思是“创建一个临时对象,并返回它”。它只调用了一次构造函数。而后者,先创建了一个对象tmp,这将调用构造函数,然后对tmp赋值,最后返回tmp。而在返回tmp时,又要创建一个临时对象,并调用拷贝构造函数用tmp对它进行初始化。在函数执行结束时,还要调用析构函数析构tmp。,几个特殊的运算符的重载,赋值运算符 下标运算符+和运算符的重载 重载函数的原型设计考虑输入输出运算符重载,输入输出运算符重载,输入输出运算符必须被重载成全局函数。输出运算符的重载输入运算符的重载,借助于流插入运算符()和流提取运算符()输入和输出用户自定义类的对象,输出重载函数的原型,ostream,实例,ostream的结果是 1/3。,为Rational类重载输出,输入输出运算符重载,输入输出运算符必须被重载成全局函数。输出运算符的重载输入运算符的重载,借助于流插入运算符()和流提取运算符()输入和输出用户自定义类的对象,输入重载函数的原型,istream,实例,istream的结果是 1/3。,为Rational类重载输入,第11章 运算符重载,什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例,类型转换-系统预定义类型间的转换,隐式类型转换 赋值时 运算时显式类型转换 强制转换法:(类型名)表达式 函数法:类型名(表达式),自定义类型转换运算符,类类型能否和其他的类类型或内置类型互相转换?内置类型之所以能互相转换是因为系统预先制定了转换的规则,并写好了完成转换的程序。类类型与其它类类型或内置类型之间如何转换,编译器预先无法知道。类的设计者必须定义转换的方法。,类型转换,内置类型到类类型的转换类类型到其它类型的转换,内置类型到类类型的转换,利用构造函数进行转换。例如,对于Rational类的对象r,可以执行r=2。此时,编译器隐式地调用Rational的构造函数,传给它一个参数2。构造函数将构造出一个num=2,den=1的Rational类的对象,并将它赋给r。,explicit构造函数,任何单参数的构造函数都可以被编译器用来执行隐式转换,即把内置类型转换成对应的类类型。在某些情况下,隐式转换是不受欢迎的。将单参数的构造函数定义为explicit,将告诉编译器不允许执行隐式转换。如将Ratioanal类的构造函数定义成 explicit Rational(int n1=0,int n2=1)则对于Rational类的对象r1和r2,执行 r1=2+r2;编译器就会报错,类型转换,内置类型到类类型的转换类类型到其它类型的转换,类类型到内置类型或其他类类型的转换,可以通过类型转换函数实现类型转换函数必须重载成成员函数 类型转换函数的格式 operator 目标类型名()const return(结果为目标类型的表达式);类型转换函数的特点无参数,无返回值是const函数,Rational类到double的转换,转换函数的定义:operator double()const return(double(num)/den);有了这个函数,我们可以将一个Rational类的对象r赋给一个double类型的变量x。如r的值为(1,3),经过赋值x=r后,x的值为0.333333,经过运算符重载后的Rational类,class Rational friend istream,Rational类的使用,int main()Rational r1,r2,r3;double x;cout r1;cout r2;r3=r1+r2;cout r1+r2=r3 endl;r3=r1*r2;cout r1*r2=r3 endl;x=r3;cout r3的double表示:x endl;return 0;,输入r1:1 3输入r2:2 61/3+1/3=2/31/3*1/3=1/9r3的double表示:0.111111,第11章 运算符重载,什么是运算符重载 运算符重载的方法 几个特殊的运算符的重载 自定义类型转换运算符 运算符重载实例,运算符重载实例,完善IntArray类,IntArray.h,#ifndef _array_h#define _array_h#include class IntArray friend ostream,public:/根据low和high为数组分配空间。分配成功,返回值为true,否则为false IntArray(int lh=0,int rh=0):low(lh),high(rh)storage=new int high-low+1;/拷贝构造函数IntArray(const IntArray#endif,IntArray.cpp,#include#include IntArray.hIntArray:IntArray(const IntArray,operator=,IntArray,operator,int,operator,ostream,operator,istream,operator=,bool operator=(const IntArray,Main函数,#include IntArray.hint main()IntArray array1(20,30),array2;cin array1;cout array1;cout array1;array2=array1;cout 执行 array2=array1,array2;cout array2;,执行结果,请输入数组元素20,30:20 21 22 23 24 25 26 27 28 29 30,cout array1=array2 是“(array1=array2)?true:false)endl;array225=0;cout 执行array25=0后,array1=array2 is(array1=array2)?true:false)endl;return 0;,执行结果,array1=array2 是true执行array25=0后,array1=array2 is false,实例研究:string类,在C语言中,string不是内置类型,不能用系统预制的运算符来进行操作。所有的操作都是通过字符串函数来实现目的:创建一种字符串类型,使之可以用预制的运算符来操作字符串。手段:通过运算符重载,class String friend ostream/destructor,String类的定义,const String,char,String类的实现私有的setString,void String:setString(const char*string2)sPtr=new char length+1;/allocate storage/terminate if memory not allocated assert(sPtr!=0);strcpy(sptr,string2);/copy literal to object,String类的实现构造,String:String(const char*s):length(strlen(s)cout Conversion constructor:s n;setString(s);/call utility function String:String(const String/call utility function,String类的实现析构,String:string()cout Destructor:sPtr n;delete sPtr;/reclaim string,String类的实现=运算符重载,const String/enables cascaded assignments,String类的实现+=运算符重载,const String/enables cascaded calls,String类的实现 布尔运算符重载,bool String:operator!()const return length=0;bool String:oprator=(const String,String类的实现 运算符重载,char/creates rvalue,String类的实现 取子串,string,/allocate memory for substring delete subPtr-sPtr;/delete character array from object subPtr-sPtr=new char subPtr-length;assert(subPtr-sPtr!=0);/ensure space allocated/copy substring into new String strncpy(subPtr-sPtr,/return new String,ostream/enables cascading,String类的实现 输入输出重载,String类的使用,int main()String s1(happy),s2(birthday),s3;/test overloaded equality and relational operators cout s1 yields s1?true:false)=s1 yields”=s1?true:false)ns2=s1 yields(s2=s1?true:false);,上页执行结果,Conversion constructor:happyConversion constructor:birthdayConversion constructor:sl is happy;s2 is birthday;s3 is The results of comparing s2 and s1:s2=s1 yields falses2!=s1 yields trues2 sl yields falses2=s1 yields falses2=s1 yields true,/test overloaded String empty(!)operator cout nnTesting!s3:n;if(!s3)cout s3 is empty;assigning s1 to s3;n;s3=s1;/test overloaded assignment cout s3 is s3;,上页执行结果,Testing!s3:s3 is empty;assigning s1 to s3;operator=calleds3 is happy,cout nns1+=s2 yields s1=;s1+=s2;/test overloaded concatenation cout s1;cout nns1+=to you yieldsn;s1+=to you;/test conversion constructor cout s1=s1 nn;,上页执行结果,s1+=s2 yields s1=happy birthdays1+=to you yieldsConversion constructor:to youDestructor:to yous1=happy birthday to you,/test overloaded function call operator()for substring cout The substring of s1 starting atn location 0 for 14 characters,s1(0,14),is:n s1(0,14)nn;/test substring to-end-of-String option cout The substring of s1 starting atn location 15,s1(15,0),is:s1(15,0)nn;/0 is to end of string,上页执行结果,Conversion constructor:The substring of sl starting atlocation 0 for 14 characters,sl(0,14),is:happy birthdayConversion constructor:The substring of sl starting atlocation 15,s1(15,0,is:to you,/test copy constructor String*s4Ptr=new String(s1);cout*s4Ptr=*s4Ptr nn;/test assignment(=)operator with self-assignment cout assigning*s4Ptr to*s4Ptrn;*s4Ptr=*s4Ptr;/test overloaded assignment cout*s4Ptr=*s4Ptr n;/test destructor delete s4Ptr;,上页执行结果,copy constructor:happy birthday to you*s4Ptr=happy birthday to youassigning*s4Ptr to*s4Ptroperator=calledAttempted assignment of a string to itself*s4Ptr=happy birthday to youdestructor:happy birthday to you,/test using subscript operator to create lvalue s1 0=H;s1 6=B;cout nsl after s1 0=H and s1 6=B is:s1 nn;/test subscript out of range cout Attempt to assign d to s1 30 yields:endl;s1 30=d;/ERROR:subscript out of range return 0;,s1 after s1 0=H and si 6=B is:Happy Birthday to youAttempt to assign d to s130 yields:Assertion failed:subscript=0&subscript length,file String1.cpp,line 76abnormal program termination,上页执行结果,小结,运算符重载的作用如何选择用成员函数或全局函数如何写一个重载函数介绍了一种区分+和的前后缀应用的方法通过运算符重载实现类类型和内置类型及其他类类型之间的转换,