版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第15章运算符重载本章要点:
教学目标本章主要介绍类的静态成员以及友元的问题。掌握静态数据成员和静态成员函数的使用,友元函数和友元类的添加。教学重点和难点◆什么是运算符重载◆一元运算符的重载◆二元运算符的重载◆通过友元函数实现重载◆输出、输入运算符的重载在前面学习类和对象的时候,我们明确了这样一个概念:定义一个类就是定义一种新类型。因此,对象和变量一样,可以作为函数的参数传递,可以作为函数的返回值的类型,也可以说明对象数组,甚至还可能有类类型的常量。在基本数据类型上,系统提供了许多预定义的运算符,它们以一种简洁的方式工作。例如“+”运算符:int x,y;y=x+y;表示两个整数相加,很简洁。但是,两个字符串合并:char x[20],y[10]; //定义两个字符串类型//……strcat(x,y);表达起来就不如“y=x+y;”那样直观简洁。因此,为了表达上的方便,希望已预定义的运算符,也可以在特定类的对象上以新的含义进行解释。如在string类对象x、y的环境下,运算符“+”能被解释为字符串x和y的合并。换言之,希望预定义运算符能被重载,让用户自定义的类也可以使用。12.1明确目标:为什么要进行运算符重载C语言中有多种内置的数据类型,例如int、float、double和char等等。对应于每一种类型都有一系列的内置运算符,比如加法运算符“+”和乘法运算符“*”。就拿运算符“+”来说吧,它可以用于int值,也可以用于float,虽然使用相同的运算符,但生成的代码不同,因为整数和浮点数在内存中的表示是不同的。这时,“+”运算符具有两种不同的解释(即实现代码)。也就是说,像“+”这样的运算符在C语言中已被重载。但是,C语言仅支持少量有限的运算符重载。C++语言对重载功能进行了扩充,也允许我们在自己定义的类上添加运算符,允许对已存在的预定义运算符由我们在不同的上下文中做出不同的解释。比如,经过运算符重载,我们可以直接对两个字符串string类对象进行加法运算stringx,y;x=x+y;可以直接输出复数类CComplex对象Ccomplexc;cout<<c;通过运算符重载,程序代码就显得简洁明了,类对象的使用也方便了。这就是我们重载运算符的目的。第一个基本动作就这么简单,没问题吧?好了,如果第一关没问题,可以继续前进了。先看看下面这段话:因为本书针对C++的入门读者,所以对一些深层次复杂的运算符重载,比如new和delete的重载以及类型转换等等不做介绍,以免倒了你的胃口。这里我们只介绍一下常用的运算符重载的实现,让大家明白运算符重载是怎么回事、如何实现,就算是完成任务了。如果想进一步学习复杂的高级的重载,可以参见别的参考书。运算符重载函数可以作为类的成员函数,也可以作为类的友元函数来实现。下面对这两种不同的实现分别讲述。通俗地说,重载运算符就是在写函数,用这个运算符函数体对重载的运算符的含义做出新的解释。这里所解释的含义与重载该运算符的类有关,比如,字符串类重载的加运算符“+”,实际上是对两个字符串进行拼接。重载后的运算符还是按这些运算符的表达方式使用。例如,在一个string类中重载了运算符“+”为两个字符串的合并,我们可以这样写:strings1,s2;s1=s1+s2; //合并串s1和串s2,存放到新串s1中。为了把运算符从头到脚地看个明明白白,我们创建一个新类:计数器类CCounter,逐步地根据需求来完善此类,并进一步地解释为什么要重载运算符以及如何来重载。例12.1创建类CCounter,类CCounter的一个对象可以用来在循环中计数(感到惊奇吧),而且这个类的对象还可以用在其它程序中以实现数字递增、递减或跟踪一些值的地方。12.2以成员函数实现运算符重载程序清单C12_01.cpp//类Counter的简单定义及实现#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} //缺省构造函数,成员变量m_val初始化为0 ~CCounter(){} //什么事都不干的析构函数 //下面两个函数是私有变量m_val的存取访问函数 unsignedGetVal()const {returnm_val;} voidSetVal(unsignedx) {m_val=x;}private: unsignedm_val;};main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl;}输出结果counter的值是:0程序分析:事实上,上面创建的CCounter类是一个“垃圾类”,没有任何用处,只有惟一的一个成员变量m_val,而且被缺省的构造函数初始化为零。用类CCounter创建的对象不能像整型变量那样进行自增和自减运算,也不能进行加法、赋值或者其它的算术操作。更恶劣的是,这个类创建的对象的输出也是一个问题,不能像整型数那样直接用cout进行输出。通过我们即将要学习的运算符重载来对CCounter类进行“教化”,使它能够像一般的整型数一样来进行一系列的算术运算。再次说明,我们只是想通过对类CCounter的逐步改造来讲解运算符重载的知识,至于这个类本身,用处倒不大。12.2.1 重载一元运算符:递增运算符“++”(1)通过添加成员函数Increment(),实现自增功能运算符重载可以使类获得使用这些运算符的“权限”。例如,我们可以通过下面两种方式让类CCounter的对象具有自增运算的功能。一种方式是通过在类CCounter中添加一个实现自增功能的方法Increment(),如下面例12.2:例12.2给CCounter类添加自增函数Increment()。程序清单C12_02.cpp#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;}//自增函数,将变量的值加1private: unsignedm_val;};main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"调用自增函数之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl;}输出结果counter的值是:0调用自增函数之后,counter的值是:1程序分析:例12.2中,在类CCounter中添加了自增函数Increment(),虽然它可以正常工作,但是使用起来和运算符“++”比较起来可是差远了,太麻烦了。似乎听见哪有抗议声,哦,原来是上面的程序在喊:“为什么不给我添加++运算符”。别急,当然可以满足它的愿望了,通过重载运算符++就可以实现。(2)重载前缀运算符“++”1) 重载前缀运算符“++”,返回类型为void要重载前缀++运算符,可以通过运算符重载函数实现,我们这里是把运算符重载函数作为类的成员函数实现的,它的语法形式如下:其中,returntype是返回值类型,operator是关键字,“op”是要重载的运算符符号,classname是重载该运算符的类的名称。重载函数的函数名是由关键字operator后面加上要重载的运算符符号组成的,为operatorop。这里我们用“op”泛指可被重载的运算符。因此,自增运算符++可以按如下的形式进行重载:voidoperator++();returntypeclassname::operatorop(参数表){//相对于该类而定义的操作代码}例12.3在CCounter类中通过运算符++的重载,实现CCounter类的自增运算++。程序清单C12_03.cpp//重载运算符++,运算符重载函数作为类CCounter的成员函数#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} voidoperator++(){++m_val;}//重载运算符++private: unsignedm_val;};main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"调用自增函数之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增运算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl;}输出结果counter的值是:0调用自增函数之后,counter的值是:1使用自增运算符++之后,counter的值是:2程序分析:在上面的程序中,函数:voidoperator++(){++m_val;}实现了运算符++的重载。在main()函数中,语句:++counter;对象counter,使用了类CCounter重载的运算符++,这种语法形式与我们期望类CCounter对象所具有的形式非常接近。但是,或许你想让类CCounter对象具有更多附加的功能,比如检查CCounter类对象是否超出了最大值范围等等。2) 重载运算符“++”,返回一个CCounter类对象,通过创建临时变量实现不知道你是否注意到,我们上面重载的自增运算符有一个很大的缺陷,那就是如果你想把CCounter类对象放在赋值运算符的右侧,那么对不起,编译器会不客气地报错,你不妨试试。把CCounter类对象放在赋值运算符的右侧,例如:CCountercounter2=++counter;这条语句试图建立一个新的CCounter类对象counter2,然后把对象counter自增后的值赋给这个对象。内置的拷贝构造函数将处理赋值运算,但是现在的自增运算符并没有返回一个CCounter类对象,而是返回空类型void,我们不能把一个空类型void对象赋给一个CCounter类对象。那怎么办?下面我们就来解决这个棘手的问题。显然,我们只需要让重载运算符函数返回一个CCounter类对象问题就解决了,这样返回的对象值就可以赋给另一个CCounter类对象了。那么返回哪个对象呢?一种方法是创建一个临时对象,然后返回。例12.4在CCounter类中重载运算符++,并返回一个CCounter类型的临时对象。程序清单C12_04.cpp//重载运算符++,并返回一个临时对象#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} CCounteroperator++();//重载++运算符,返回值为CCounter类型private: unsignedm_val;};CCounterCCounter::operator++(){ ++m_val; CCountertemp; temp.SetVal(m_val); returntemp;}main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"调用自增函数之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增运算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; CCountercounter2=++counter; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl;}输出结果counter的值是:0调用自增函数之后,counter的值是:1使用自增运算符++之后,counter的值是:2counter2的值是:3counter的值是:3程序分析:在上面这个版本的程序中,运算符重载函数operator++的返回值为一个CCounter类对象。在函数operator++中,我们创建了一个临时对象temp:CCountertemp;而且通过语句temp.SetVal(m_val);将对象temp的值设置为当前对象的值。临时对象被返回而且赋值给对象counter2。CCountercounter2=++counter;至此,上面的实现好像是很完美了,但是,有一个问题,就是:为什么我们要创建一个临时对象?”,记住:每个临时对象被创建而且使用完之后必须要被销毁——这是一个潜在的“奢侈”的操作,要耗费资源,况且对象counter已经存在,并且已经有正确的值,那么为什么不直接返回它呢?我们可以通过使用this指针来解决这个问题。3)重载运算符“++”,通过this指针返回一个CCounter类对象的引用正如我们在第13章中讨论的那样,this指针是类的所有成员函数的隐含参数,因为运算符重载函数operator++是类的成员函数,所以this指针是该函数的隐含参数。this指针指向对象counter,如果间接引用this指针(*this)则其返回对象counter,对象counter的成员变量m_val已经有正确的值。例12.5在CCounter重载运算符函数中返回间接引用this指针的值,这样就可以避免创建一个临时对象,从而可以提高程序运行的效率。程序清单C12_05.CPP//返回this指针的间接引用#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} constCCounter&operator++();//重载++运算符,返回this指针的间接引用private: unsignedm_val;};constCCounter&CCounter::operator++(){ ++m_val; return*this;}main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"调用自增函数之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增运算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; CCountercounter2=++counter; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl;}输出结果counter的值是:0调用自增函数之后,counter的值是:1使用自增运算符++之后,counter的值是:2counter2的值是:3counter的值是:3程序分析:在上面的程序中,通过间接引用this指针,返回当前对象的引用,并将当前对象的值赋给对象counter2。注意:上面程序中返回的是当前对象的引用,因此就避免了创建多余的临时对象。因为要防止当前的对象值被别的函数改变,所以返回值是const限定的常量引用(回忆一下const限定的使用)。好了,现在类CCounter已经有了一个使用方便,性能卓越的++运算符了。大家自然的会想到前面的++运算符是前缀自增,那后缀自增运算符又该如何重载呢?想的周到,值得表扬。我们下面就看看重载后缀自增运算符如何实现。(3)重载后缀运算符“++”首先来看看编译器是如何区别前缀自增和后缀自增运算的?按照约定,在运算符重载函数的声明中,提供一个整型数作为函数的参数,就表示重载的是后缀运算符。参数值并没有什么用,它只是用来表明重载的是后缀运算符。在我们重载后缀自增运算符之前,先要彻底搞清楚到底它与前缀自增运算符有什么不同。如果忘记了,请回头去看看前面的C语言部分。前面我们讲到,前缀自增是“先自增,然后再拿来参与运算”,而后缀自增相反,它是“先拿来用,然后变量再自增”。因此,重载前缀运算符只要简单地递增变量的值,然后返回对象本身即可,而后缀运算符必须返回进行自增运算之前的对象的值。为了做到这一点,我们必须创建一个临时对象,用它来存储对象的初始值,然后增加对象的初始值,最后返回临时对象。让我们通过下面的小例子再简单地回顾一下后缀运算符的使用。m=n++;如果n的值是6,执行完上述语句之后,m的值是6,但是n的值是7。这样,我们返回了变量n原来的值,并且赋给变量m,然后将n的值加1。如果n是一个对象,它的后缀自增运算必须先将对象原来的值(如6)存储到一个临时对象中,然后增加n的值(n的值变为7),最后返回临时对象的值并赋给变量m。注意:因为我们返回的是临时对象,所以必须返回对象的值,而不能是返回引用,这是因为临时对象在函数返回时就超出了它的作用范围,将被销毁,如果返回它的引用,那么这个引用将是一个已经不存在的对象的引用,其后果是不可预料的。例12.6在CCounter中实现后缀和前缀自增运算符的重载。程序清单C12_06.cpp//重载后缀和前缀自增运算符#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} constCCounter&operator++(); //重载前缀++运算符 constCCounteroperator++(int); /*重载后缀++运算符,带一个整型参数, 表示是后缀运算符*/private: unsignedm_val;};constCCounter&CCounter::operator++(){ ++m_val; return*this;}constCCounterCCounter::operator++(int){ CCountertemp; temp.m_val=m_val; //将对象原有的值保存到临时对象中 ++m_val; //自增对象的值 returntemp; //返回临时对象}main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"调用自增函数之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增运算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; CCountercounter2=++counter; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter2=counter++; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl;}输出结果counter的值是:0调用自增函数之后,counter的值是:1使用自增运算符++之后,counter的值是:2counter2的值是:3counter的值是:3counter2的值是:3counter的值是:4在上面的程序中,分别实现了前缀和后缀运算符的重载。注意,在main()函数中使用后缀++运算符时,并没有在它的后面加一个整型的标志,而是像其它内置数据类型一样的来使用后缀++运算符。前面我们已经强调,在后缀运算符重载函数中带一个整型参数,只是为了表明是后缀运算符,其所带的参数值从不被使用。至此,我们实现了前缀和后缀的++运算符的重载。前缀和后缀的自减运算符--的重载完全类似。你不妨自己在类CCounter中添加进去(不要偷懒呦!)。重载运算符事实上就是在实现一个函数,函数的函数名由关键字operator和要重载的运算符组成。如果运算符重载函数是类的成员函数(如上面的示例程序),那么重载一元运算符的函数不带参数。注意,如果重载的是后缀运算符,如++或--,那么不要忘记带一个整型参数作为标志。例如:constCCounter&CCounter::operator++(); //前缀++CCounterCCounter::operator--(int); //后缀--12.2.2二元运算符重载:重载加运算符“+”前面我们讲述了一元运算符前缀自增和后缀自增的重载,它们只是在一个对象上操作。加法运算符“+”是二元运算符,是对两个对象进行操作。如何对类CCounter实现加运算(+)的重载呢?我们的目标是声明两个CCounter类对象,然后对它们进行加运算,例如:CCountercounter1,counter2,counter3;counter3=counter1+counter2;(1)添加成员函数Add(),实现对象相加的功能实现加法功能的第一种方式是:通过在类CCounter中添加一个成员函数Add(),该函数以一个CCounter类对象作为参数,将对象参数的值和当前对象的值相加,然后返回一个CCounter类对象。例12.7在CCounter类中添加Add()函数,实现两个CCounter对象相加。程序清单C12_07.cpp#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} CCounter(unsignedinitVal):m_val(initVal){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} CCounterAdd(constCCounter&cnt);private: unsignedm_val;};CCounterCCounter::Add(constCCounter&cnt){ CCountertemp; temp.SetVal(m_val+cnt.GetVal()); returntemp;}main(){ CCountercounter1(1),counter2(3),counter3; //创建三个CCounter类对象 counter3=counter1.Add(counter2); //调用成员函数Add() cout<<"counter1的值是:"<<counter1.GetVal()<<endl; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter3=counter1+ounter2的值是:"<<counter3.GetVal()<<endl;}输出结果counter1的值是:1counter2的值是:3counter3=counter1+counter2的值是:4(2)重载加运算符“+”我们实现的Add()函数虽然可以正常工作了,但是使用起来不够简单自然。不说大家可能也会想到,我们实现两个CCounter类对象进行加运算的另外一种方法是重载运算符“+”,使加运算可以写为如下形式:CCountercounter1,counter2,counter3;counter3=counter1+counter2;例12.8在CCounter类中实现重载运算符“+”。程序清单C12_08.cpp//重载运算符"+"#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} CCounter(unsignedinitVal):m_val(initVal){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} CCounteroperator+(constCCounter&cnt); //重载加(+)运算符private: unsignedm_val;};CCounterCCounter::operator+(constCCounter&cnt){ CCountertemp; temp.SetVal(m_val+cnt.GetVal()); returntemp;}main(){ CCountercounter1(2),counter2(4),counter3; //创建三个CCounter类对象 counter3=counter1+counter2; cout<<"counter1的值是:"<<counter1.GetVal()<<endl; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter3=counter1+counter2的值是:"<<counter3.GetVal()<<endl;}输出结果counter1的值是:2counter2的值是:4counter3=counter1+counter2的值是:6程序分析:在上面的程序中,重载了加(+)运算符。将加运算符重载函数的声明和实现分别与Add()函数的声明和实现,做个比较,它们几乎是一模一样,只是它们的调用语法不同,使用下面的调用方式(2)显然要比方式(1)简单自然:(1)counter3=counter1.Add(counter2);(2)counter3=counter1+counter2;虽然没有多大的变化,但已经足以使程序易于理解,代码书写也更方便了。注意:重载加运算符的方法同样适用于其它的二元运算符,比如,减(-)操作等等。除了重载二元运算符的函数带有一个参数之外,其它的都和重载一元运算符类似。重载运算符函数的参数是对象的常值引用类型。例如:CounterCounter::operator+(constCounter&cnt);//重载+运算符CounterCounter::operator-(constCounter&cnt);//重载-运算符在上面的程序中,我们实现了二元运算符“+”的重载,其它二元运算符的重载类似,大家可以尝试对类CCounter重载“-”运算符。12.2.3 重载赋值运算符“=”赋值运算符“=”可以被重载,而且必须被重载为成员函数,其重载格式为:其中A表示类名,source是一个A类型的对象。当用户在一个类中显式地重载了赋值运算符“=”时,称用户定义了类赋值运算。它将一个A类的对象source逐域拷贝(拷贝所有的成员)到赋值号左端的类对象中。如果用户没有为一个类重载赋值运算符,编译程序将生成一个缺省的赋值运算符。赋值运算把源对象的值逐域地拷贝到目标对象中。对许多简单的类,如前面的CCounter类,缺省的赋值函数工作的很好。AA::operator=(constAsource){//复制对象source的所有成员}但是,如果用户定义的类中有指针成员,牵扯到内存的分配问题,那么系统提供的缺省的赋值运算就不能正确工作,用户必须显式地为类定义赋值运算。此问题类似于我们在第13章讲到的拷贝构造函数。拷贝构造函数和赋值运算符都是把一个对象的数据成员拷贝到另一个对象,它们的函数体实现非常类似,但是它们是有区别的。拷贝构造函数是要以一个已存在的对象来创建一个新对象,而赋值运算符则是改变一个已存在的对象的值。我们在前面的部分提到过,如果我们自己不定义拷贝构造函数,那么系统会自动提供一个。同样,如果我们对自己定义的类,不定义赋值运算,那么系统也会提供默认的赋值运算操作(operator=)。无论什么时候你使用对象进行赋值时,这个操作都会被调用。例如:CCatcat1(2,4),cat2(5,7);//其它程序代码cat2=cat1;注:关于CCat类的定义,请看第13章的示例13.2。在上面这段代码中,创建了两个CCat类对象:cat1和cat2,并且把cat1的成员变量m_age和m_weight分别初始化为2和4,把cat2的成员变量m_age和m_weight分别初始化为5和7。在执行过一些程序代码之后,将对象cat1的值赋给cat2,别看这小小的一个举动,却会引发两个潜在的问题:首先,如果成员m_age是一个指针而不是整型变量,会导致什么情况发生?其次,对象cat2的原有的值会发生什么变化?在第13章我们讨论拷贝构造函数的时候,就提出了关于成员变量如果是指针类型的情况,对于赋值运算有同样的问题。在C++的程序中,浅拷贝(逐域拷贝)不同于深拷贝,前者只是简单地拷贝对象的成员。如果类中有指针成员,那么如果使用浅拷贝将导致两个对象将指向同一块存储空间;而深拷贝不同,它会另外分配一块空间给目标对象的指针变量。例12.9在CCat类中定义赋值运算,即重载赋值运算符。程序清单C12_09.cpp//重载赋值运算符=,重载函数中包括预防自复制的代码#include<iostream>#include<string>usingnamespacestd;classCCat{public: CCat(); //缺省构造函数 intGetAge()const{return*m_age;} char*GetName()const{returnm_name;} voidSetAge(intage){*m_age=age;} voidSetName(char*name); CCatoperator=(constCCat&); //重载赋值运算符private: int*m_age; char*m_name;};CCat::CCat(){ m_age=newint; //给指针分配空间 *m_age=5; m_name=newchar[20]; //给Name指针分配空间 strcpy(m_name,"Mypet");}voidCCat::SetName(char*name){ delete[]m_name; m_name=newchar[strlen(name)+1]; strcpy(m_name,name);}CCatCCat::operator=(constCCat&sCat){ //如果源对象的地址和this指针相等,说明它们是同一个对象,是自赋值 if(this==&sCat) return*this; delete[]m_name; //删除原有的空间 m_name=newchar[strlen(sCat.m_name)+1]; //分配新的存储空间 *m_age=sCat.GetAge(); strcpy(m_name,sCat.m_name); //将源对象的值赋给当前对象 return*this; //返回当前对象}main(){ CCatcat1; cat1.SetAge(2); cout<<"cat1的年龄是:"<<cat1.GetAge()<<endl <<"昵称:"<<cat1.GetName()<<endl; CCatcat2; cat2.SetName("SunFlower"); cout<<"cat2的年龄是:"<<cat2.GetAge()<<endl <<"昵称:"<<cat2.GetName()<<endl; cout<<"\n把cat1的值赋给cat2...\n\n"; cat2=cat1; cout<<"cat2的年龄是:"<<cat2.GetAge()<<endl <<"昵称:"<<cat1.GetName()<<endl;}输出结果cat1的年龄是:2昵称:Mypetcat2的年龄是:5昵称:SunFlower把cat1的值赋给cat2...cat2的年龄是:2昵称:Mypet上面CCat类的定义可能把你都搞糊涂了,我想你肯定不理解为什么要把成员变量声明为如下的形式:int*m_age;char*m_name;没错,这样做是有点让人费解。之所以这样做,目的只是为了说明:当成员变量中有指针时,应该如何重载赋值运算符。我们在赋值运算符的重载函数中,通过语句“if(this==&sCat)return*this;”检查了自赋值的情况。这样做的目的是处理由于当对象cat2作为赋值运算符的右操作数将它的值都拷贝到内存中后(cat2=cat2;),接着它会释放它占用的存储空间(delete[]m_name;),所导致的一个非常大的问题:存储空间没了。更重要的是,当我们使用引用或者间接的引用指针时这种自赋值的情况很可能发生。比如:CCatcat2;//其它的程序代码CCat&cat3=cat1;//其它的程序代码cat2=cat3;//自赋值这样通过重载赋值运算符,很好地解决了我们上面提到的两个问题,一个是当成员变量是指针的问题,另一个是自赋值的问题。12.3.1用友元函数重载加法运算符“+”有些时候,用成员函数重载运算符会碰到一些麻烦。例如:例12.10在复数类CComplex中用成员函数重载运算符“+”。程序清单C12.10.cpp//用成员函数重载运算符"+"classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} CComplexoperator+(constCComplex&); //重载的加运算符 ~CComplex(){};private: double real; //复数的实部 double image; //复数的虚部};12.3用友元函数重载运算符CComplexCComplex::operator+(constCComplex&c){ CComplex temp; temp.real=real+c.real; temp.image=image+c.image; returntemp;}main(){ CComplexc1(1,5),c2(3); c1=c2+16; //正确:被编译器解释为c1=c2.operator+(CComplex(16)) c1=16+c2; //错误:被解释为:c1=16.operator+(c2);}在上面的程序段中,语句“c1=c2+16;”可被解释为“c1=c2.operator+(16)”。c2是一个复数类CComplex对象,系统用的是复数类中重载的运算符“+”。所需要的参数应该是CComplex类对象,虽然上述语句中给出的参数是整数16,但是系统可以自动的将16进行类型转换,它通过构造函数“CComplex(doublerv){real=rv;image=0.0;}”,将整数16变为CComplex类类型常量CComplex(16)。因此上述语句可以正常工作。但是,语句“c1=16+c2;”将被解释为“c1=16.operator+(c2);”。这句不会逃过编译器的考核,因为16不是用户定义的CComplex类对象,在程序的上下文中,编译器并不知道用户重载的运算符“+”的含义,所以只好用系统预定义的“+”运算符的含义进行解释,显然,整数常量16不能与复数对象c2进行加法,所以这个语句不能正常工作。在上述代码中,我们把“+”运算符作为CComplex类的成员函数进行重载,不具有交换性。这是因为作为成员函数重载的运算符“+”仅能通过实际的对象所调用。如果引起调用的是一个非CComplex类对象的值,比如上面的整型数16,则该成员函数就不知何去何从了。那我们该如何做才能让“+”运算符恢复它的灵活性,具有可交换性呢?C++语言的开发者,当然替我们想得很周到了,提供了“灵丹妙药”来解决此问题了。这就是将运算符重载函数作为友元函数来实现。因为友元函数没有隐含的this指针,所以用友元函数实现运算符重载时,该运算符的操作数都必须在友元函数的参数表中明确声明。修改例12.10的程序,将“+”运算符的重载函数作为类CComplex的友元函数来实现。具体实现代码如下:例12.11在CComplex类中使用友元函数重载运算符。程序清单C12.11.cpp//链表类CLinkList的使用//用友元函数重载运算符"+"classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} friendCComplexoperator+(CComplexc1,CComplexc2); //作为类的友元函数,重载加运算符。 ~CComplex(){}private: double real; //复数的实部 double image; //复数的虚部};CComplexoperator+(CComplexc1,CComplexc2){ CComplex temp; temp.real=c1.real+c2.real; temp.image=c1.image+c2.image; returntemp;}main(){ CComplexc1(1,5),c2(3); c1=c2+16; //正确:被编译器解释为c1=operator+(c2,CComplex(16)) c1=16+c2; //正确:被编译器解释为c1=operator+(CComplex(16),c2)}这次就不会受编译器的“责难”了,可以顺利地通过编译。那么什么时候使用成员函数实现重载,什么时候又用友元函数实现呢?有些运算符必须用成员函数重载,比如赋值运算符“=”、取下标运算符“[]”、函数调用运算符“()”以及间接指针运算符“->”。但是有些时候,比如例12.10中的情况,我们只能通过友元函数重载来实现,还有输出运算符“<<”也只能通过友元函数来重载(原因在讲解例12.12时会说明)。总之,二者选择其一,要视具体情况而定。下面给出作为成员函数或是友元函数来实现运算符重载的不同之处:1)因为成员函数有隐含的this指针,所以在用成员函数重载运算符时,this指针可以作为一个参数使用。这样,如果重载的运算符是一元运算符,则运算符重载函数不需要参数,因为this指针所指的对象就是运算符的操作数;如果重载的是二元运算符,那么运算符重载函数只需要带一个参数,其中参数表示运算符的右操作数,this指针所指的对象则是运算符的左操作数。形式如下://成员函数形式,重载一元运算符返回类型C::operatorop(){ //类C对op的解释}//成员函数形式,重载二元运算符返回类型C::operatorop(C&b) { //类C对op的解释}其中,C是类名,op是要重载的运算符,b是参数。2)因为友元函数没有隐含的this指针,所以用友元函数实现运算符重载时,该运算符的操作数都必须在友元函数的参数表中明确声明。如果重载的运算符是一元运算符,则运算符重载函数就要带一个参数,表示操作数;如果重载的是二元运算符,那么运算符重载函数就要带两个参数,分别表示两个操作数。形式如下://作为类C的友员函数,重载一元运算符返回类型operatorop(C&a){ //对op的解释}//作为类C的友员函数,重载二元运算符返回类型operatorop(C&a,C&b){ //对op的解释}其中,C是类名,op是要重载的运算符,a、b是参数。12.3.2重载输出运算符“<<”下面我们再来看一个使用友元函数实现重载输出运算符“<<”的例子。例12.12在复数类CComplex中重载输出运算符“<<”。程序清单C12_12.cpp//重载输出运算符"<<"#include<iostream.h> //有些编译系统可能是包含iostream,并指明名字空间std;classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} friendCComplexoperator+(CComplexc1,CComplexc2); //作为类的友元函数,重载加运算符 friendostream&operator<<(ostream&stream,CComplexc); //重载输出运算符"<<" ~CComplex(){};private: double real; //复数的实部 double image; //复数的虚部};CComplexoperator+(CComplexc1,CComplexc2){ CComplex temp; temp.real=c1.real+c2.real; temp.image=c1.image+c2.image; returntemp;}ostream&operator<<(ostream&stream,CComplexc){ stream<<“(”<<c.real<<“+”<<c.image<<“i)”<<endl; //以(a+bi)的格式输出复数 returnstream;}main(){ CComplex c1(1,5),c2(3); cout<<"c1="<<c1; //使用重载输出运算符"<<",输出复数c1 cout<<"c2="<<c2; //使用重载输出运算符"<<",输出复数c2 c1=c2+16; cout<<"执行语句c1=c2+16;之后,"; cout<<"c1="<<c1;}输出结果c1=(1+5i)c2=(3+0i)执行语句c1=c2+16;之后,c1=(19+0i)运算符重载的限制:重载运算符时,不能改变它们的优先级,也不能改变它们的结合性。当然如果运算符是一元的,我们不能把它重载为二元运算符。也就是说,我们不能改变运算符所需要的操作数的数目,更不能创造出新的运算符。比如,不能声明运算符“**”来表示求操作数的平方。如果这样,就违反了我们重载运算符的初衷。进行运算符重载要注意的问题:在C++的新手的编程工作中,运算符重载是最容易被滥用的C++特性之一。因为对一些含义模糊的运算符进行重载,使其具有有意义的功能,这是非常有诱惑力的。但是,盲目地重载运算符会导致代码混乱,难于理解。毫无疑问,通过重载,可以使运算符“+”实现减法的功能,而使运算符“*”用来进行加法,这些也是非常有趣的事情,但是没有专业的程序员会这么干。这样做的最大的危险是在于企图是好的,但是却使运算符变了“质”。一定要记住,重载运算符的目的是增强代码的可理解性,可不要背道而驰,异想天开地盲目地重载运算符。注意:只有在重载运算符有益于代码清晰的情况下,再实行重载。不要窜改运算符原有的意义。在前面学习类和对象的时候,我们明确了这样一个概念:定义一个类就是定义一种新类型。因此,对象和变量一样,可以作为函数的参数传递,可以作为函数的返回值的类型,也可以说明对象数组,甚至还可能有类类型的常量。在基本数据类型上,系统提供了许多预定义的运算符,它们以一种简洁的方式工作。例如“+”运算符:int x,y;y=x+y;表示两个整数相加,很简洁。但是,两个字符串合并:char x[20],y[10]; //定义两个字符串类型//……strcat(x,y);表达起来就不如“y=x+y;”那样直观简洁。因此,为了表达上的方便,希望已预定义的运算符,也可以在特定类的对象上以新的含义进行解释。如在string类对象x、y的环境下,运算符“+”能被解释为字符串x和y的合并。换言之,希望预定义运算符能被重载,让用户自定义的类也可以使用。12.4应用举例例12.13修改类CPeople的实现,重载类的输出运算符和赋值运算符。程序清单People.h//类CPeople的定义#include<iostream>usingnamespacestd;classCPeople{ intm_nAge; floatm_fSalary;public: char*m_pstrName; CPeople(); ~CPeople(); CPeople(intage,floatsalary,char*name); floatGetSalary()const; voidSetSalary(float); intGetAge()const; voidSetAge(intage); CPeople&operator=(constCPeople&AnotherPeople); friendostream&operator<<(ostream&stream,constCPeople&p);};程序清单People.cpp//类CPeople的实现#include"People.h"#include<string>usingnamespacestd;CPeople::CPeople(){ m_nAge=20; m_fSalary=3000.00f; m_pstrName=newchar[20]; strcpy(m_pstrName,"无名氏");}CPeople::CPeople(intage,floatsalary,char*name){ m_nAge=age; m_fSalary=salary; m_pstrName=newchar[20]; strcpy(m_pstrName,name);}//类CPeople的实现#include"People.h"#include<string>usingnamespacestd;CPeople::CPeople(){ m_nAge=20; m_fSalary=3000.00f; m_pstrName=newchar[20]; strcpy(m_pstrName,"无名氏");}CPeople::CPeople(intage,floatsalary,char*name){ m_nAge=age; m_fSalary=salary; m_pstrName=newchar[20]; strcpy(m_pstrName,name);}CPeople::~CPeople(){ if(m_pstrName!=NULL) delete[]m_pstrName;}intCPeople::GetAge()const{ returnm_nAge;}voidCPeople::SetAge(intage){ m_nAge=age;}floatCPeople::GetSalary()const{ if(m_nAge<20) return0; elseif(GetAge()>60) returnm_fSalary/2; else returnm_fSalary;}voidCPeople::SetSalary(floatnum){ m_fSalary=num;}//赋值运算符重载的实现CPeople&CPeople::operator=(constCPeople&AnotherPeople){ if(this==&AnotherPeople) //检查自赋值 return*this; if(m_pstrName) delete[]m_pstrName; //释放原有的内存资源 m_nAge=AnotherPeople.m_nAge; m_fSalary=AnotherPeople.m_fSalary; //分配新的内存资源,并复制内容 m_pstrName=newchar[strlen(AnotherPeople.m_pstrName)+1]; strcpy(m_pstrName,AnotherPeople.m_pstrName); return*this;}//输出运算符重载函数的实现ostream&operator<<(ostream&stream,constCPeople&p){ stream<<"姓名:"<<p.m_pstrName<<"," <<"年龄:"<<p.GetAge()<<"," <<"薪水:"<<p.GetSalary(); returnstream;}程序清单C12_13.cpp//测试CPeople类#include<iostream>#include"people.h"usingnamespacestd;main(){ CPeopleZhang(65,2000.00f,"张飞"); cout<<Zhang<<endl; //输出对象的值 CPeopleA,B; A.SetAge(34); A.SetSalary(4000.00f); cout<<A<<endl; B=A; //将对象A的值赋给对象B cout<<B<<endl;}输出结果姓名:张飞,年龄:65,薪水:1000姓名:无名氏,年龄:34,薪水:4000姓名:无名氏,年龄:34,薪水:4000程序分析在上面的程序中,我们重新定义了类CPeople,重载了赋值运算符“=”和输出运算符“<<”。重载赋值运算符的函数体实现与拷贝构造函数的实现非常相似(参照例13.12)。在赋值运算符重载函数中,我们进行了自赋值检查,从而确保类对象的赋值运算可以正常工作。重载了CPeople类的输出运算符,使对象的输出变得简单好用。例12.14在复数类CComplex中实现=、+、-、++、<<运算符的。程序清单complex.h#include<iostream.h>classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} ~CComplex(){} CComplex&operator=(constCComplex&); CComplexoperator+(constCComplex&); CComplexoperator-(constCComplex&); CComplex&operator++(); //前缀 CComplexoperator++(int); //后缀 friendostream&operator<<(ostream&stream,CComplexc);private: double real; double image;};程序清单complex.cpp#include<iostream.h>#include"complex.h"CComplex&CComplex::operator=(constCComplex&c){ //判断是否自复制 if(this==&c) return*this; real=c.real; image=c.image; return*this;}CComplexCComplex::operator+(constCComplex&c){ CComplex temp; temp.real=real+c.real; temp.image=image+c.image; returntemp;}CComplexCComplex::operator-(constCComplex&c){ CComplex temp; temp.real=this->real-c.real; temp.image=this->image-c.image; returntemp;}CComplex&CComplex::operator++() //前缀{ real++; image++; return*this;}CComplexCComplex::operator++(inta) //后缀{ CComplextemp; temp.real=real; temp.image=image; real++; image++; returntemp;}ostream&operator<<(ostream&stream,CComplexc){ cout<<c.image; stream<<"("<<c.real<<"+"<<c.image<<"i)"; //以格式(a+bi)显示 returnstream;}程序清单C12_14.cpp#include<iostream.h>#include"complex.h"main(){ CComplexz(2.0,3.0),k(3.0,4.0),x(1.0),y; cout<<"z="<<z<<","<<"k="<<k<<endl; cout<<"x="<<x<<","<<"y="<<y<<endl; k++; x=z-k; y=z+x++; cout<<"经过如下计算:\nk++\n"<<"x=z-k\n"<<"y=z+x++之后\n"; cout<<"===================="<<endl; cout<<"z="<<z<<","<<"k="<<k<<endl; cout<<"x="<<x<<","<<"y="<<y<<endl;}输出结果z=(2+3i),k=(3+4i)x=(1+0i),
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 信托法培训讲义
- 审计机关财务审计培训
- 《各税种的会计核算》课件
- 受戒与破戒的冲突与和谐
- 社区护士家庭访视的沟通唐莹教授护患沟通护患关系护士培训
- 《员工培训教材范本》课件
- 员工培训前须知
- 蚌埠三中2020-2021学年高一第二学期4月月考化学答案
- 心理学的研究内容
- 智慧养老智能家居项目功能架构设计智慧养老技术概论
- 雷沃十年十大影响力事件评选活动方案
- 风电相关书籍18
- 全员育人导师制工作手册
- GIS安装施工方案
- 矿区基本情况(简介)
- 最新办公楼物业交接表格资料
- 钳工教学中钻孔方法的改进探究
- 水轮机结构介绍(经典)
- 高处作业基本知识高处不胜寒安全不能忘
- 南苑校区集团考核自评报告
- 浅谈智能化工程总包管理及智能化工程深化设计
评论
0/150
提交评论