1、 本章要点:什么是运算符重载什么是运算符重载一元运算符的重载一元运算符的重载二元运算符的重载二元运算符的重载通过友元函数实现重载通过友元函数实现重载输出、输入运算符的重载输出、输入运算符的重载在前面学习类和对象的时候,我们明确了这样一个概念:定义一个类就是定义一种新类型。因此,对象和变量一样,可以作为函数的参数传递,可以作为函数的返回值的类型,也可以说明对象数组,甚至还可能有类类型的常量。在基本数据类型上,系统提供了许多预定义的运算符,它们以一种简洁的方式工作。例如“+”运算符:intx,y;y=x+y;表示两个整数相加,很简洁。但是,两个字符串合并:char x20,y10;/定义两个字符串
2、类型/strcat(x,y);表达起来就不如“y=x+y;”那样直观简洁。因此,为了表达上的方便,希望已预定义的运算符,也可以在特定类的对象上以新的含义进行解释。如在string类对象x、y的环境下,运算符“+”能被解释为字符串x和y的合并。换言之,希望预定义运算符能被重载,让用户自定义的类也可以使用。一、为什么要进行运算符重载C语言中有多种内置的数据类型,例如int、float、double和char等等。对应于每一种类型都有一系列的内置运算符,比如加法运算符“+”和乘法运算符“*”。就拿运算符“+”来说吧,它可以用于int值,也可以用于float,虽然使用相同的运算符,但生成的代码不同,因
3、为整数和浮点数在内存中的表示是不同的。这时,“+”运算符具有两种不同的解释(即实现代码)。也就是说,像“+”这样的运算符在C语言中已被重载。但是,C语言仅支持少量有限的运算符重载。C+语言对重载功能进行了扩充,也允许我们在自己定义的类上添加运算符,允许对已存在的预定义运算符由我们在不同的上下文中做出不同的解释。比如,经过运算符重载,我们可以直接对两个字符串string类对象进行加法运算string x,y;x=x+y;可以直接输出复数类CComplex对象Ccomplex c;cout c;通过运算符重载,程序代码就显得简洁明了,类对象的使用也方便了。这就是我们重载运算符的目的。第一个基本动作
4、就这么简单,没问题吧?好了,如果第一关没问题,可以继续前进了。先看看下面这段话:因为本书针对C+的入门读者,所以对一些深层次复杂的运算符重载,比如new和delete的重载以及类型转换等等不做介绍,以免倒了你的胃口。这里我们只介绍一下常用的运算符重载的实现,让大家明白运算符重载是怎么回事、如何实现,就算是完成任务了。如果想进一步学习复杂的高级的重载,可以参见别的参考书。运算符重载函数可以作为类的成员函数,也可以作为类的友元函数来实现。下面对这两种不同的实现分别讲述。通俗地说,重载运算符就是在写函数,用这个运算符函数体对重载的运算符的含义做出新的解释。这里所解释的含义与重载该运算符的类有关,比如
5、,字符串类重载的加运算符“+”,实际上是对两个字符串进行拼接。重载后的运算符还是按这些运算符的表达方式使用。例如,在一个string类中重载了运算符“+”为两个字符串的合并,我们可以这样写:string s1,s2;s1=s1+s2;/合并串s1和串s2,存放到新串s1中。为了把运算符从头到脚地看个明明白白,我们创建一个新类:计数器类CCounter,逐步地根据需求来完善此类,并进一步地解释为什么要重载运算符以及如何来重载。例15.1 创建类CCounter,类CCounter的一个对象可以用来在循环中计数(感到惊奇吧),而且这个类的对象还可以用在其它程序中以实现数字递增、递减或跟踪一些值的地
6、方。二、以成员函数实现运算符重载程序清单 C15_01.cpp/类类Counter的简单定义及实现的简单定义及实现#include using namespace std;class CCounterpublic:CCounter():m_val(0)/缺省构造函数,成员变量缺省构造函数,成员变量m_val初始化为初始化为0CCounter()/什么事都不干的析构函数什么事都不干的析构函数/下面两个函数是私有变量下面两个函数是私有变量m_val的存取访问函数的存取访问函数unsigned GetVal()const return m_val;void SetVal(unsigned x)m_v
7、al=x;private:unsigned m_val;main()CCounter counter;cout counter的值是:的值是:counter.GetVal()endl;输出结果输出结果counter的值是:0程序分析:事实上,上面创建的CCounter类是一个“垃圾类”,没有任何用处,只有惟一的一个成员变量m_val,而且被缺省的构造函数初始化为零。用类CCounter创建的对象不能像整型变量那样进行自增和自减运算,也不能进行加法、赋值或者其它的算术操作。更恶劣的是,这个类创建的对象的输出也是一个问题,不能像整型数那样直接用cout进行输出。通过我们即将要学习的运算符重载来对C
8、Counter类进行“教化”,使它能够像一般的整型数一样来进行一系列的算术运算。再次说明,我们只是想通过对类CCounter的逐步改造来讲解运算符重载的知识,至于这个类本身,用处倒不大。1.重载一元运算符:递增运算符重载一元运算符:递增运算符“+”(1)通过添加成员函数)通过添加成员函数Increment(),实现自增,实现自增功能功能运算符重载可以使类获得使用这些运算符的运算符重载可以使类获得使用这些运算符的“权限权限”。例。例如,我们可以通过下面两种方式让类如,我们可以通过下面两种方式让类CCounter的对象具有自的对象具有自增运算的功能。一种方式是通过在类增运算的功能。一种方式是通过在
9、类CCounter中添加一个实中添加一个实现自增功能的方法现自增功能的方法Increment(),如下面例,如下面例15.2:例例15.2 给给CCounter类添加自增函数类添加自增函数Increment()。程序清单程序清单 C15_02.cpp#include using namespace std;class CCounterpublic:CCounter():m_val(0)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;void Increment()+m_val;/自增函数,
10、将变量的值加自增函数,将变量的值加1private:unsigned m_val;main()CCounter counter;cout counter的值是:的值是:counter.GetVal()endl;counter.Increment();cout 调用自增函数之后,调用自增函数之后,;cout counter的值是:的值是:counter.GetVal()endl;输出结果输出结果counter的值是:0调用自增函数之后,counter的值是:1程序分析:程序分析:例例15.2中,在类中,在类CCounter中添加了自增函数中添加了自增函数Increment(),虽然它可以正常工作
11、,但是使用起,虽然它可以正常工作,但是使用起来和运算符来和运算符“+”比较起来可是差远了,太麻烦了。比较起来可是差远了,太麻烦了。似乎听见哪有抗议声,哦,原来是上面的程序在喊:似乎听见哪有抗议声,哦,原来是上面的程序在喊:“为什么不给我添加为什么不给我添加+运算符运算符”。别急,当然可以。别急,当然可以满足它的愿望了,通过重载运算符满足它的愿望了,通过重载运算符+就可以实现。就可以实现。(2)重载前缀运算符“+”1)重载前缀运算符“+”,返回类型为void要重载前缀+运算符,可以通过运算符重载函数实现,我们这里是把运算符重载函数作为类的成员函数实现的,它的语法形式如下:其中,returntyp
12、e是返回值类型,operator是关键字,“op”是要重载的运算符符号,classname是重载该运算符的类的名称。重载函数的函数名是由关键字operator后面加上要重载的运算符符号组成的,为operator op。这里我们用“op”泛指可被重载的运算符。因此,自增运算符+可以按如下的形式进行重载:void operator+();returntype classname:operator op(参数表参数表)/相对于该类而定义的操作代码相对于该类而定义的操作代码例15.3 在CCounter类中通过运算符+的重载,实现CCounter类的自增运算+。程序清单 C15_03.cpp/重载运算
13、符重载运算符+,运算符重载函数作为类,运算符重载函数作为类CCounter的成员函数的成员函数#include using namespace std;class CCounterpublic:CCounter():m_val(0)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;void Increment()+m_val;void operator+()+m_val;/重载运算符重载运算符+private:unsigned m_val;main()CCounter counter;cou
14、t 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程序分析:在上面的程序中,函数:void operator+()+m_v
15、al;实现了运算符+的重载。在main()函数中,语句:+counter;对象counter,使用了类CCounter重载的运算符+,这种语法形式与我们期望类CCounter对象所具有的形式非常接近。但是,或许你想让类CCounter对象具有更多附加的功能,比如检查CCounter类对象是否超出了最大值范围等等。2)重载运算符“+”,返回一个CCounter类对象,通过创建临时变量实现不知道你是否注意到,我们上面重载的自增运算符有一个很大的缺陷,那就是如果你想把CCounter类对象放在赋值运算符的右侧,那么对不起,编译器会不客气地报错,你不妨试试。把CCounter类对象放在赋值运算符的右侧
16、,例如:CCounter counter2=+counter;这条语句试图建立一个新的CCounter类对象counter2,然后把对象counter自增后的值赋给这个对象。内置的拷贝构造函数将处理赋值运算,但是现在的自增运算符并没有返回一个CCounter类对象,而是返回空类型void,我们不能把一个空类型void对象赋给一个CCounter类对象。那怎么办?下面我们就来解决这个棘手的问题。显然,我们只需要让重载运算符函数返回一个CCounter类对象问题就解决了,这样返回的对象值就可以赋给另一个CCounter类对象了。那么返回哪个对象呢?一种方法是创建一个临时对象,然后返回。例15.4
17、在CCounter类中重载运算符+,并返回一个CCounter类型的临时对象。程序清单 C15_04.cpp/重载运算符重载运算符+,并返回一个临时对象,并返回一个临时对象#include using namespace std;class CCounterpublic:CCounter():m_val(0)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;void Increment()+m_val;CCounter operator+();/重载重载+运算符,返回值为运算符,返回值为CC
18、ounter类型类型private:unsigned m_val;CCounter CCounter:operator+()+m_val;CCounter temp;temp.SetVal(m_val);return temp;main()CCounter counter;cout counter的值是:的值是:counter.GetVal()endl;counter.Increment();cout 调用自增函数之后,调用自增函数之后,;cout counter的值是:的值是:counter.GetVal()endl;+counter;cout 使用自增运算符使用自增运算符+之后之后,;co
19、ut counter的值是:的值是:counter.GetVal()endl;CCounter counter2=+counter;cout counter2的值是:的值是:counter2.GetVal()endl;cout counter的值是:的值是:counter.GetVal()endl;输出结果输出结果counter的值是:0调用自增函数之后,counter的值是:1使用自增运算符+之后,counter的值是:2counter2的值是:3counter的值是:3 程序分析:在上面这个版本的程序中,运算符重载函数operator+的返回值为一个CCounter类对象。在函数opera
20、tor+中,我们创建了一个临时对象temp:CCounter temp;而且通过语句temp.SetVal(m_val);将对象temp的值设置为当前对象的值。临时对象被返回而且赋值给对象counter2。CCounter counter2=+counter;至此,上面的实现好像是很完美了,但是,有一个问题,就是:为什么我们要创建一个临时对象?”,记住:每个临时对象被创建而且使用完之后必须要被销毁这是一个潜在的“奢侈”的操作,要耗费资源,况且对象counter已经存在,并且已经有正确的值,那么为什么不直接返回它呢?我们可以通过使用this指针来解决这个问题。3)重载运算符“+”,通过this指
21、针返回一个CCounter类对象的引用正如我们在第13章中讨论的那样,this指针是类的所有成员函数的隐含参数,因为运算符重载函数operator+是类的成员函数,所以this指针是该函数的隐含参数。this指针指向对象counter,如果间接引用this指针(*this)则其返回对象counter,对象counter的成员变量m_val已经有正确的值。例15.5 在CCounter重载运算符函数中返回间接引用this指针的值,这样就可以避免创建一个临时对象,从而可以提高程序运行的效率。程序清单程序清单 C15_05.CPP/返回返回this指针的间接引用指针的间接引用#include usi
22、ng namespace std;class CCounterpublic:CCounter():m_val(0)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;void Increment()+m_val;const CCounter&operator+();/重载重载+运算符,返回运算符,返回this指针的间接引用指针的间接引用private:unsigned m_val;const CCounter&CCounter:operator+()+m_val;return*this;mai
23、n()CCounter counter;cout counter的值是:的值是:counter.GetVal()endl;counter.Increment();cout 调用自增函数之后,调用自增函数之后,;cout counter的值是:的值是:counter.GetVal()endl;+counter;cout 使用自增运算符使用自增运算符+之后之后,;cout counter的值是:的值是:counter.GetVal()endl;CCounter counter2=+counter;cout counter2的值是:的值是:counter2.GetVal()endl;cout cou
24、nter的值是:的值是:counter.GetVal()endl;输出结果输出结果counter的值是:0调用自增函数之后,counter的值是:1使用自增运算符+之后,counter的值是:2counter2的值是:3counter的值是:3程序分析:在上面的程序中,通过间接引用this指针,返回当前对象的引用,并将当前对象的值赋给对象counter2。注意:上面程序中返回的是当前对象的引用,因此就避免了创建多余的临时对象。因为要防止当前的对象值被别的函数改变,所以返回值是const限定的常量引用(回忆一下const限定的使用)。好了,现在类CCounter已经有了一个使用方便,性能卓越的+
25、运算符了。大家自然的会想到前面的+运算符是前缀自增,那后缀自增运算符又该如何重载呢?想的周到,值得表扬。我们下面就看看重载后缀自增运算符如何实现。(3)重载后缀运算符“+”首先来看看编译器是如何区别前缀自增和后缀自增运算的?按照约定,在运算符重载函数的声明中,提供一个整型数作为函数的参数,就表示重载的是后缀运算符。参数值并没有什么用,它只是用来表明重载的是后缀运算符。在我们重载后缀自增运算符之前,先要彻底搞清楚到底它与前缀自增运算符有什么不同。如果忘记了,请回头去看看前面的C语言部分。前面我们讲到,前缀自增是“先自增,然后再拿来参与运算”,而后缀自增相反,它是“先拿来用,然后变量再自增”。因此
26、,重载前缀运算符只要简单地递增变量的值,然后返回对象本身即可,而后缀运算符必须返回进行自增运算之前的对象的值。为了做到这一点,我们必须创建一个临时对象,用它来存储对象的初始值,然后增加对象的初始值,最后返回临时对象。让我们通过下面的小例子再简单地回顾一下后缀运算符的使让我们通过下面的小例子再简单地回顾一下后缀运算符的使用。用。m=n+;如果如果n的值是的值是6,执行完上述语句之后,执行完上述语句之后,m的值是的值是6,但是,但是n的的值是值是7。这样,我们返回了变量。这样,我们返回了变量n原来的值,并且赋给变量原来的值,并且赋给变量m,然,然后将后将n的值加的值加1。如果。如果n是一个对象,它
27、的后缀自增运算必须先将是一个对象,它的后缀自增运算必须先将对象原来的值(如对象原来的值(如6)存储到一个临时对象中,然后增加)存储到一个临时对象中,然后增加n的值的值(n的值变为的值变为7),最后返回临时对象的值并赋给变量),最后返回临时对象的值并赋给变量m。注意:因为我们返回的是临时对象,所以必须返回对象的值,注意:因为我们返回的是临时对象,所以必须返回对象的值,而不能是返回引用,这是因为临时对象在函数返回时就超出了它而不能是返回引用,这是因为临时对象在函数返回时就超出了它的作用范围,将被销毁,如果返回它的引用,那么这个引用将是的作用范围,将被销毁,如果返回它的引用,那么这个引用将是一个已经
28、不存在的对象的引用,其后果是不可预料的。一个已经不存在的对象的引用,其后果是不可预料的。例例15.6 在在CCounter中实现后缀和前缀自增运算符的重载。中实现后缀和前缀自增运算符的重载。程序清单程序清单 C15_06.cpp/重载后缀和前缀自增运算符重载后缀和前缀自增运算符#include using namespace std;class CCounterpublic:CCounter():m_val(0)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;void Increment(
29、)+m_val;const CCounter&operator+();/重载前缀重载前缀+运算符运算符const CCounter operator+(int);/*重载后缀重载后缀+运算符,带一个整型参数,运算符,带一个整型参数,表示是后缀运算符表示是后缀运算符*/private:unsigned m_val;const CCounter&CCounter:operator+()+m_val;return*this;const CCounter CCounter:operator+(int)CCounter temp;temp.m_val=m_val;/将对象原有的值保存到临时对象中将对象原
30、有的值保存到临时对象中+m_val;/自增对象的值自增对象的值return temp;/返回临时对象返回临时对象main()CCounter counter;cout counter的值是:的值是:counter.GetVal()endl;counter.Increment();cout 调用自增函数之后,调用自增函数之后,;cout counter的值是:的值是:counter.GetVal()endl;+counter;cout 使用自增运算符使用自增运算符+之后之后,;cout counter的值是:的值是:counter.GetVal()endl;CCounter counter2=+
31、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的值是
32、:的值是:3counter的值是:的值是:3counter2的值是:的值是:3counter的值是:的值是:4在上面的程序中,分别实现了前缀和后缀运算符的重载。注意,在main()函数中使用后缀+运算符时,并没有在它的后面加一个整型的标志,而是像其它内置数据类型一样的来使用后缀+运算符。前面我们已经强调,在后缀运算符重载函数中带一个整型参数,只是为了表明是后缀运算符,其所带的参数值从不被使用。至此,我们实现了前缀和后缀的+运算符的重载。前缀和后缀的自减运算符-的重载完全类似。你不妨自己在类CCounter中添加进去(不要偷懒呦!)。重载运算符事实上就是在实现一个函数,函数的函数名由关键字ope
33、rator和要重载的运算符组成。如果运算符重载函数是类的成员函数(如上面的示例程序),那么重载一元运算符的函数不带参数。注意,如果重载的是后缀运算符,如+或-,那么不要忘记带一个整型参数作为标志。例如:const CCounter&CCounter:operator+();/前缀+CCounter CCounter:operator-(int);/后缀-2.二元运算符重载:重载加运算符“+”前面我们讲述了一元运算符前缀自增和后缀自增的重载,它们只是在一个对象上操作。加法运算符“+”是二元运算符,是对两个对象进行操作。如何对类CCounter实现加运算(+)的重载呢?我们的目标是声明两个CCou
34、nter类对象,然后对它们进行加运算,例如:CCounter counter1,counter2,counter3;counter3=counter1+counter2;(1)添加成员函数Add(),实现对象相加的功能实现加法功能的第一种方式是:通过在类CCounter中添加一个成员函数Add(),该函数以一个CCounter类对象作为参数,将对象参数的值和当前对象的值相加,然后返回一个CCounter类对象。例15.7 在CCounter类中添加Add()函数,实现两个CCounter对象相加。程序清单程序清单 C15_07.cpp#include using namespace std;c
35、lass CCounterpublic:CCounter():m_val(0)CCounter(unsigned initVal):m_val(initVal)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;CCounter Add(const CCounter&cnt);private:unsigned m_val;CCounter CCounter:Add(const CCounter&cnt)CCounter temp;temp.SetVal(m_val+cnt.GetVal();r
36、eturn temp;main()CCounter counter1(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的值是:1coun
37、ter2的值是:3counter3=counter1+counter2的值是:4(2)重载加运算符“+”我们实现的Add()函数虽然可以正常工作了,但是使用起来不够简单自然。不说大家可能也会想到,我们实现两个CCounter类对象进行加运算的另外一种方法是重载运算符“+”,使加运算可以写为如下形式:CCounter counter1,counter2,counter3;counter3=counter1+counter2;例15.8 在CCounter类中实现重载运算符“+”。程序清单程序清单 C15_08.cpp/重载运算符重载运算符+#include using namespace std
38、;class CCounterpublic:CCounter():m_val(0)CCounter(unsigned initVal):m_val(initVal)CCounter()unsigned GetVal()const return m_val;void SetVal(unsigned x)m_val=x;CCounter operator+(const CCounter&cnt);/重载加重载加(+)运算符运算符private:unsigned m_val;CCounter CCounter:operator+(const CCounter&cnt)CCounter temp;te
39、mp.SetVal(m_val+cnt.GetVal();return temp;main()CCounter counter1(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的
40、值是:2counter2的值是:4counter3=counter1+counter2的值是:6 程序分析:在上面的程序中,重载了加(+)运算符。将加运算符重载函数的声明和实现分别与Add()函数的声明和实现,做个比较,它们几乎是一模一样,只是它们的调用语法不同,使用下面的调用方式(2)显然要比方式(1)简单自然:(1)counter3=counter1.Add(counter2);(2)counter3=counter1+counter2;虽然没有多大的变化,但已经足以使程序易于理解,代码书写也更方便了。注意:重载加运算符的方法同样适用于其它的二元运算符,比如,减(-)操作等等。除了重载二元
41、运算符的函数带有一个参数之外,其它的都和重载一元运算符类似。重载运算符函数的参数是对象的常值引用类型。例如:Counter Counter:operator+(const Counter&cnt);/重载+运算符Counter Counter:operator-(const Counter&cnt);/重载-运算符在上面的程序中,我们实现了二元运算符“+”的重载,其它二元运算符的重载类似,大家可以尝试对类CCounter重载“-”运算符。3.重载赋值运算符“=”赋值运算符“=”可以被重载,而且必须被重载为成员函数,其重载格式为:其中A表示类名,source是一个A类型的对象。当用户在一个类中显
42、式地重载了赋值运算符“=”时,称用户定义了类赋值运算。它将一个A类的对象source逐域拷贝(拷贝所有的成员)到赋值号左端的类对象中。如果用户没有为一个类重载赋值运算符,编译程序将生成一个缺省的赋值运算符。赋值运算把源对象的值逐域地拷贝到目标对象中。对许多简单的类,如前面的CCounter类,缺省的赋值函数工作的很好。A A:operator=(const A source)/复制对象复制对象source的所有成员的所有成员但是,如果用户定义的类中有指针成员,牵扯到内存的分配问题,那么系统提供的缺省的赋值运算就不能正确工作,用户必须显式地为类定义赋值运算。此问题类似于我们在第13章讲到的拷贝构
43、造函数。拷贝构造函数和赋值运算符都是把一个对象的数据成员拷贝到另一个对象,它们的函数体实现非常类似,但是它们是有区别的。拷贝构造函数是要以一个已存在的对象来创建一个新对象,而赋值运算符则是改变一个已存在的对象的值。我们在前面的部分提到过,如果我们自己不定义拷贝构造函数,那么系统会自动提供一个。同样,如果我们对自己定义的类,不定义赋值运算,那么系统也会提供默认的赋值运算操作(operator=)。无论什么时候你使用对象进行赋值时,这个操作都会被调用。例如:CCat cat1(2,4),cat2(5,7);/其它程序代码cat2=cat1;注:关于CCat类的定义,请看第13章的示例13.2。在上
44、面这段代码中,创建了两个CCat类对象:cat1和cat2,并且把cat1的成员变量m_age和m_weight分别初始化为2和4,把cat2的成员变量m_age和m_weight分别初始化为5和7。在执行过一些程序代码之后,将对象cat1的值赋给cat2,别看这小小的一个举动,却会引发两个潜在的问题:首先,如果成员m_age是一个指针而不是整型变量,会导致什么情况发生?其次,对象cat2的原有的值会发生什么变化?在第13章我们讨论拷贝构造函数的时候,就提出了关于成员变量如果是指针类型的情况,对于赋值运算有同样的问题。在C+的程序中,浅拷贝(逐域拷贝)不同于深拷贝,前者只是简单地拷贝对象的成员
45、。如果类中有指针成员,那么如果使用浅拷贝将导致两个对象将指向同一块存储空间;而深拷贝不同,它会另外分配一块空间给目标对象的指针变量。例例15.9 在在CCat类中定义赋值运算,即重载赋值运算符。类中定义赋值运算,即重载赋值运算符。程序清单程序清单 C15_09.cpp/重载赋值运算符重载赋值运算符=,重载函数中包括预防自复制的代码,重载函数中包括预防自复制的代码#include#include using namespace std;class CCatpublic:CCat();/缺省构造函数缺省构造函数int GetAge()const return*m_age;char*GetName(
46、)const return m_name;void SetAge(int age)*m_age=age;void SetName(char*name);CCat operator=(const CCat&);/重载赋值运算符重载赋值运算符private:int*m_age;char*m_name;CCat:CCat()m_age=new int;/给指针分配空间给指针分配空间*m_age=5;m_name=new char20;/给给Name指针分配空间指针分配空间strcpy(m_name,Mypet);void CCat:SetName(char*name)delete m_name;m_
47、name=new charstrlen(name)+1;strcpy(m_name,name);CCat CCat:operator=(const CCat&sCat)/如果源对象的地址和如果源对象的地址和this指针相等,说明它们是同一个对象,是自赋值指针相等,说明它们是同一个对象,是自赋值if(this=&sCat)return*this;delete m_name;/删除原有的空间删除原有的空间m_name=new charstrlen(sCat.m_name)+1;/分配新的存储空间分配新的存储空间*m_age=sCat.GetAge();strcpy(m_name,sCat.m_na
48、me);/将源对象的值赋给当前对象将源对象的值赋给当前对象return*this;/返回当前对象返回当前对象main()CCat cat1;cat1.SetAge(2);cout cat1的年龄是的年龄是:cat1.GetAge()endl 昵称:昵称:cat1.GetName()endl;CCat cat2;cat2.SetName(SunFlower);cout cat2的年龄是的年龄是:cat2.GetAge()endl 昵称:昵称:cat2.GetName()endl;cout n把把cat1的值赋给的值赋给cat2.nn;cat2=cat1;cout cat2的年龄是的年龄是:cat
49、2.GetAge()endl 昵称:昵称:cat1.GetName()”。但是有些时候,比如例15.10中的情况,我们只能通过友元函数重载来实现,还有输出运算符“”也只能通过友元函数来重载(原因在讲解例15.12时会说明)。总之,二者选择其一,要视具体情况而定。下面给出作为成员函数或是友元函数来实现运算符重载的不同之处:1)因为成员函数有隐含的this指针,所以在用成员函数重载运算符时,this指针可以作为一个参数使用。这样,如果重载的运算符是一元运算符,则运算符重载函数不需要参数,因为this指针所指的对象就是运算符的操作数;如果重载的是二元运算符,那么运算符重载函数只需要带一个参数,其中参
50、数表示运算符的右操作数,this指针所指的对象则是运算符的左操作数。形式如下:/成员函数形式,重载一元运算符成员函数形式,重载一元运算符返回类型返回类型 C:operator op()/类类C对对op的解释的解释/成员函数形式,重载二元运算符成员函数形式,重载二元运算符 返回类型返回类型 C:operator op(C&b)/类类C对对op的解释的解释其中,C是类名,op是要重载的运算符,b是参数。2)因为友元函数没有隐含的this指针,所以用友元函数实现运算符重载时,该运算符的操作数都必须在友元函数的参数表中明确声明。如果重载的运算符是一元运算符,则运算符重载函数就要带一个参数,表示操作数;