专题:C++中操作符的重载

一篇老文章,原本在网易博客的,结果博客关停了。

      操作符重载涉及到一些类设计方面的东西,同时也有C++中名字搜索等。

 

      下面是C++标准中说明的可以被重载的操作符:

new delete new[] delete[]

+          -          *         /         %        ˆ          &          |        ∼

!          =         <        >       +=     -=           *=        /=     %=

ˆ=       &=      |=     <<      >>     >>=     <<=      ==     !=

<=      >=     &&     ||     ++     --     ,     ->*     ->    ( )     [ ] 

      其中同时可用于一元和二元的操作符有     +    -    *    &

      不可被重载的操作符有     .     .*     ::     ?:

      上面的4个操作符不能被重载是因为其参数为名称,而非值。

      同样不能被重载的还有sizeof和typeid,当然还有四个C++引入的强制转换符static_cast, const_cast, dynamic_cast, reinterpret_cast。

      关于操作符的重载,我们按类型可以分为一元操作符和二元操作符。其中一元操作符(前缀或者后缀)既可以定义为无参数的非静态成员函数,也可以定义为取一个参数的非成员函数;解释的规则是:对于操作符@,@aa可以被解释为aa.operator()或者operator@(aa)。对于二元操作符既可以定义为取一个参数的非静态成员函数,也可以定义为取两个参数的非成员函数;解释规则是:对于操作符@,aa@bb可以解释为aa.operator@(bb)或者operator@(aa, bb)。不管是一元还是二元的操作符重载,如果用两种重载方式定义了操作符,那么按照重载解析规则来确定到底是要用哪一个,不过成员函数版的操作符重载并不比非成员函数的操作符重载优先级高。还有一点要注意,在非成员函数的定义中,必须至少有一个参数是用户自定义类型,因为这一点要保证不改变原有表达式的意义。

      函数定义的形参和函数调用时的实参之间的匹配原则是:尽可能调用匹配的最好的那个函数。下面是具体的参数匹配原则:

[1] 准确匹配,也就是说无需任何转换,或者只需做普通转换(比如数组名到指针,函数名到函数指针,T到const T等)的匹配。

[2] 利用提升的匹配,就是包括整数提升(bool到int,char到int,short到int以及对应的无符号版本)以及float到double的提升。

[3] 利用标准转换(int到double, double到int,double到long double,Derived*到Base*,T*到void*,int到unsinged int)的匹配。

[4] 利用用户定义转换的匹配。

[5] 利用在函数声明中的省略号...的匹配

      如果以上匹配得到的不止一个函数,那么将存在歧义,编译器不会通过的。

      运算符重载函数也是位于某一个命名空间的,相同的名字在不同的命名空间中是不同的。这里就涉及到重载解析的时候关于命名空间对匹配的影响。并且如果没有下面这条规则,我们就无法实现输入输出操作符(<<, >>)的定义,因为这个符号操作很特别。

假如操作符@是二元操作符,x是类型X,y是类型Y,那么x@y将会按照如下的方式解析:

  • 若X是类,查找作为X的成员函数,或者X的某个基类的成员函数的operator@。
  • 在围绕x@y的环境中查找operator@的声明,并且:
  • 若X在命名空间N中定义,在N中查找operator@的声明
  • 若Y在命名空间M中定义,在M中查找operator@的声明

以上规则,如果有一条成功,就不会往后执行了。一元操作符的规则也类似。关于命名空间、名字查找和接口规则在[B.4]中的第5章中有精彩的描述,Hurb Sutter告诉我们有时候(很碰巧),实际被调用的函数并不是我们所想的那样。

      对于那些可以被重载的操作符,我们最好不要重载&&, ||和逗号操作符(,),因为我们无法实现&&和||的短路求值规则,因为C++没有规定参数的求值顺序,以及无法实现逗号操作符(参考[B.5]中的条款7)。

      为了减少重载函数的个数,一般地,我们可以用inexplicit的构造函数转换来实现将一种数据转换为自定义类型,当然这种隐式转换最多只能做一次;反过来,我们也可以将自定义类型转化为某一种类型,只要我们重新定义强制转换的符号即可,这个定义有点像构造函数,不能有返回值,但是和构造函数不同的是它可以return想要的值。当然了,大多数时候我们并不想让隐式转换发生,这时候我们应该用explicit来修饰单参数构造函数;如果我们想要自定义类型转为另一种类型的值,可以直接定义这种成员函数,比如说asString(),asInt()……

      有时候我们为了定义一个操作,来让两种自定义的类型作为其参数,这时候我们可以使用友元函数。我们可以在两个类中将这个操作符的重载函数声明为友元函数,从而可以让任何一个都可以作为对象来调用其operator。

      对于不同的操作符,我们是应该将其实现为成员函数,或者非成员函数,或者静态成员函数,或者友元函数,再或者某几种都可以,或者只能是某种。这里面就有一些情况需要一一说清楚,并且还有一些重载操作符方面的技巧和好的经验需要说明一下。

      关于隐式转换有一点要提出,就是非const类型不能用于隐式转换:

      double &d = 3.14;              //error

      const double& cd = 3.14;  //ok

      [B.2]的11.5.2中意思是,当我们在考虑重载操作符的时候,应该先考虑是否可把某操作设计为自由函数,再考虑是把该函数设计为成员函数还是友元函数。

      基于前述内容,具体考虑如何设计重载操作符函数的方法是这样的:

  • 如果该操作要修改对象的状态,那么这个函数要设计成员函数或者带有一个非const引用参数的自由函数(最好位于某命名空间);
  • 那些操作基础类型的左值运算符最好设计为成员函数;
  • 如果希望参与运算的参数都能够隐式转换,那么应该设计为自由函数,并且去const引用参数,或者非引用参数;
  • 如果运算符经常要访问其运算对象的内部表示,那么可以考虑将其设计为友元函数;
  • 如果要让该操作符有效,就必须定义类型转换,那么最好使用成员函数而不是使用带引用参数的友元函数。另外,如果最后很难抉择,那么优先选择成员函数,成员函数的表达式要比等价的自由函数的表达式短的多。

 

      很显然,重载的操作符最好要与内置类型保持一致。下面就讲一些常用的、有一些技巧的操作符重载:

  • 如果定义了二元运算符+、-、*、/、%、^(位运算异或)、&(位运算与)、|(位运算或)、<<(左移位)、>>(右移位),~(位运算取反),那么最好定义对应的复合运算符,并且可以使用复合运算符来实现对应的二元运算符;
  • 定义一元运算符+(正)、-(负)、*(解引用)、&(取地址),可以为成员函数,也可以是自由函数,没有必要是友元函数;
  • ==和!=,>=和<=,>和<最好成对出现,还有前置自增++和后置自增++,前置自减--和后置自减--最好成对出现(使用一个不带形参的int来区分,带有int就是后置操作);
  • 逻辑&&、||和逗号运算符最好不要定义;
  • 输入输出运算符<<、>>只能定义为非成员函数(为了写出来的代码不至于出现data<<cout;这样的形式);
  • 赋值运算符=比较特别,必须也只能定义为成员函数,因为这同时还是赋值构造函数;
  • 函数调用运算符(),可以定义为成员函数,也可以(但没有太多的理由)定义为自由函数;
  • 类型转换运算符operator type()const,有时候我们并不想发生这类隐式转换,C++11之后我们明确指出是否允许隐式调用该转换符,可以像限制单参数构造函数那样使用explicit;
  • 成员访问操作符->,这个在智能指针中见得多,一般定义为成员函数。
  • 逻辑非!,这个见得不多,可以定义为成员函数,也可以(但没有太多的理由)定义为自由函数;

      不过,我有点奇怪为什么没有~=复合操作符,并且几乎没见过->*(数据成员指针访问符)的定义。

      关于new/delete的重载参考我的另二篇文章[D.1],[D.2]。

      最后,至于重载中的一些两可的重载函数可以根据自己的喜好,关于一些返回引用参数的重载,至于要不要用const修饰,也是看具体的应用场景。

 

 

 

 

 

参考资料

Doc

[D.1]  C++中new与delete的定制  

[D.2]  placement new/delete释疑  

 

Book

[B.1]  Stanley B. Lippman,《C++ Primer 5th》中文版/英文版

[B.2]  Bjarne Stroustrup,《The C++ Programming Language 4th Edition》

[B.3] C++标准文档,ISO/IEC 14882,13.5 Overloaded operators

[B.4] Herb Sutter,《Exceptional C++

[B.5] Scott Meyers,《More Effective C++

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值