第8章 运算符重载
8.1 运算符函数据与运算符重载
运算符重载是计算机语言固有多态性的体现,是构成计算机语言的基础之一。
C++把重载的运算符视为特殊的函数,称为运算符函数。运算符重载就是函数重载的一种特殊情况。像对待一般重载函数一样,编译系统能够依据使用运算符的不同环境,即参数(操作数)的数量或类型的差异,区分同一运算符的不同含义。
“运算符重载”是针对C++中原有运算符进行的,不可能通过重载创造出新的运算符。除了。、。*、->*、::、?:这五个运算符外,其他运算符都可以重载。由于很多符号是一元运算符和二元运算符公用的,为了避免含混,不得为重载的运算符函数设置默认值,调用时也就不得省略实参。
除了new和delete这两个较为特殊运算符以外,任何运算符如果作为成员函数重载时不得重载为静态函数。=、[ ]、()、->以及所有的类型转换运算符只能作为成员函数重载,而且不能是针对枚举类型操作数的重载。
运算符函数的函数名是由运算符前加关键字operator构成的,在声明运算符或调用运算符时都可以用这个名称。
8.2 典范运算符的重载
1.关于分数类fraction
fraction的声明和定义包含在头文件fraction.h和程序文件fraction.cpp中。
一个标准的用fraction表示的分数须满足以下复印件:
①分母永远为正,分数和符号用分子表示;
②分子分母互质,即总表示为最简分数。
fraction通过两个私有数据成员num和den分别保存分子和分母,并在必要时调用standardize函数进行标准化处理,以使num和den的值满足标准分数的条件。gcd是求两个整数的最大公约数的函数,standardize在化简分数时要调用它。
2.重载取负运算符“-”
因为fraction用分子的符号代表整个分数的符号,因此所谓“取负”只需对分子num取负就可以了。由于取负运算符“-”是一元运算符,当作为成员函数重载时参数表中没有参数,那个唯一的操作数以this指针的形式隐藏在参数表中。为此,只需要在fraction.h的类声明中增加:
fraction poerator -()const { return fraction(-num,den);}
就可以了。由于在类声明中直接给出了完整定义,因此是一个inline函数。
“-”是一个典型的一元运算符,除++、--外的其他一元运算符的重载都可以参考这里描述的方法。
3.重载加法运算符“+”
“ +”是一个二元运算符,因此作为成员函数重载时参数表中只有一个参数,对应于第二操作数,而第一操作数就是对象本身,仅以this指针的形式隐藏在参灵敏表中。
“+”是一个典型的二元运算符,除赋值类运算符外的其他二元运算符的重载都可以参考这里描述的方法。
4.重载增1运算符“+ +”
+ +既可以是前缀运算符(前增1),又可以是后缀运算符(后增1)。为了区分这两种情况,重载这两个运算符时必须在格式上有所区别:重载后缀+ +时必须多一个虚拟参数:int,因此从形式上看像是一个二元运算符重载。
5.重载类型转换符“long”
类型转换符必须作为成员函数重载。在重载类型转换符时,由于运算符本身已经表示出返回值类型,因此不需要返回值类型的声明。一个分数可以看成是由一个整数部分和一个纯分数部分组成的,为了取得一个分数的整数部分,可为fraction重载类型转换符long.为此可在fraction.h的类声明中增加:
opertator long()const { return num/den;}
6.重载赋值运算符“=”
赋值运算符只能作为成员函数重载。
常见的真正需要重载赋值运算符的情况是:类中包含指向动态空间的指针
赋值运算符=的重载应注意以下几点:
①返回值声明为引用,而函数体中总是用语句return *this;返回;
②如果参数被声明为指向同类对象的引用或指针,应判别所指向对象的是否与被赋值对象为同一对象,如果是,立即返回,不做任何赋值处理;
③如果被赋值对象占用了动态空间或其他资源,应首先释放这些资源,以便接收新的资源;
④如果参数被声明为指针或引用,通常应加上const修饰;
⑤如果参数被声明为指针,应判别是否为空,以便做出特殊处理;
⑥一个类如果需要重载运算符=,通常也就需要定义自己特有的拷贝构造函数,反之亦然。
7.重载复合赋值运算符“+=”
重载复合赋值类运算符,如+=、-=等,也应遵循上述重载赋值运算符的注意事项。
与赋值运算符不同的是,复合赋值类运算符既可作为成员函数重载也可作为非成员函数重载。在后一种情况下,两个操作数都必须出现在参数表中;为了保持运算符原有的特性,第一参数应当声明为引用(否则就无法改变它的值),返回值也应当像重载“=”那样声明为引用,并在最后将获得新值的第一参数返回。
8.重载关系操作符“>”
重载的关系操作符函数应返回逻辑值。对于 fraction的两个对象,可以通过比较通分后的两个分子来确定它们的大小。为此,可在fraction.h的类声明中增加如下的成员函数声明:
bool operator>(fraction f){ return num*f.den>f.num*den;}
其他关系运算符可以参照重载。
9重载下标访问运算符“[ ]”
运算符[ ]只能作为成员函数重载。
10重载C+ +流运算符“”和“”
C+ +流的输入运算符和输出运算符只能作为非类成员函数重载。在一个类中,如有必要,可将或声明为友元函数。
8.3 运算符重载应注意的几个问题
1.重载的运算符应保持其原有的基本语义
重载的运算符应该体现为原运算符的功能在新的数据类型上的延伸,它的使用应当使程序中算法的表达显得更流畅、自然,使阅读程序的人在不借助于其他说明资料的情况下就能够正确理解。不要让重载的运算符去勉强承担那些更适于一般函数承担的功能。
2.生载的运算符应尽可能保持基原有的特性
运算符的操作数个数、优先级和结合性是三个最基本的特性,而且是重载时自然得以保持的特性,因此无须采取专门的措施。需要注意的是下面这些特性。
①是否要求第一操作数为有左值操作数。
②是否修改第一操作数。
③操作的结果是否为有左值数据。
④应保证第二操作数不被改变。
3.运算符的重载应当配套
某些运算符之间关系密切,存在着某种逻辑上的联系,因此若需要重载其中的某一个,往往就意味着同组的其他运算符也需要重载。
4.使用引用参数还是非引用参数?
非引用参数的优点是:以传值方式传递参数,形参变量只是实参的副本,对形参变量的修改不会影响实参;在相关对象存在只需一个实参的构造函数的情况下,可以充分利用表达式处理过程中的自动转换机制,使表达式显得更自然。但当对象很大或需要深层复制时,非引用参数占用的计算机资源较多,影响参数传递的效率。
引用参数的优点是:当对象很大或需要深层复制时,可大大减少对资源的占用,提高参数传递的效率。但无法利用系统的自动转换机制。
5.作为成员函数重载还是作为非成员函数重载?
=、[ ]、()、->以及所有的类型转换运算符只能作为成员函数重载。如果允许第一操作数不是同类对象,而是其他数据类型,则只能作为非成员函数重开车(如输入输出流运算符和就是这样的情况)。若希望系统在必要时能够利用只需一个实参的构造函数自动对第一操作数进行转换,也应将该运算符作为非成员函数重载;此种情况下,运算符函数的参数应该是非引用参数,否则不能达到所希望的效果。其他情况下一般应作为成员函数重载。
北京 | 天津 | 上海 | 江苏 | 山东 |
安徽 | 浙江 | 江西 | 福建 | 深圳 |
广东 | 河北 | 湖南 | 广西 | 河南 |
海南 | 湖北 | 四川 | 重庆 | 云南 |
贵州 | 西藏 | 新疆 | 陕西 | 山西 |
宁夏 | 甘肃 | 青海 | 辽宁 | 吉林 |
黑龙江 | 内蒙古 |