第14章-重载运算符与类型转换

来源:互联网 发布:python 3.0 廖雪峰 编辑:程序博客网 时间:2024/04/29 16:48

重载运算符与类型转换


1 基本概念

  • 当运算符作用于内置类型的运算对象时,我们无法改变该运算符的含义(不能为内置对象运算符重新定义)。
  • 只能重载已有的运算符,而无权发明新的运算符。
  • 对于一个重载的运算符来说,优先级和结合律与对应的内置运算符保持一致。
  • 不能被重载的运算符有:作用域运算符(::),.*运算符,.运算符和?:运算符。
  • data1+data2;与operator+(data1,data2)等价(这是对于非成员运算符重载)。
  • data1+=data2;与data1.operator+=(data2)等价(这是对于成员运算符重载)。
  • 对于指定运算对象求值顺序的运算符(&&和||)来说,因为它们的重载版本无法保留求值顺序和/或短路求值属性,因此不建议重载它们。
  • 对于逗号运算符和取地址运算符,C++语言已经定义了这两种运算符用于类类型对象时的特殊含义,这与大多数运算符都不相同。因为它们已经有了内置的含义,所以一般不应该重载它们。
  • 在重载运算符的时候,尽量使用与内置类型一致的含义。
  • 对于将运算符定义为成员函数还是普通的非成员函数,应该遵循下列准则:
  • 赋值(=)、下标([])、调用(())和成员访问箭头运算符(->)必须是成员;
  • 复合赋值运算(+=、-=等)一般来说应该是成员,但是非必须;
  • 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员;
  • 具有对称性的运算符可能转换任意一端的运算对象,例如算数、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数。
  • 当我们把运算符定义成成员函数时,它的左侧运算对象必须是运算符所属类的一个对象。


2 输入和输出运算符

2-1 重载输出运算符<<

  • 通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符,相反,应该让用户有权控制输出的细节。

2-2 重载输入运算符>>

  • 输入运算符必须处理输出可能失败的情况,而输出运算符就不需要。
  • 当读取操作发生错误时,输入运算符应该负责从错误中恢复。


3 算术和关系运算符

  • 如果类同时定义了算术运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值运算符来实现算数运算符。

3-1 相等运算符

  • 相等运算符和不相等运算符中的一个应该把工作委托给另外一个,这意味着其中一个运算符应该负责实际比较对象的工作,而另一个运算符则只是调用那个真正工作的运算符。

3-2 关系运算符

  • 如果类同时也含有==运算符的话,则定义一种关系另其与==保持一致。特别是,如果两个对象是不相等的,那么必须结果是一个对象<另外一个。


4 赋值运算符


5 下标运算符

  • 如果一个类包含下标运算符,则它通常会应以两个版本:一个返回普通引用,另一个是类的常量成员并且返回常量引用。


6 递增和递减运算符


7 成员访问运算符

  • 我们可以令operator*完成任何我们指定的操作。但是箭头运算符不是这样,它永远不能丢掉成员访问这个最基本的含义。而当我们重载箭头运算符时,可以改变的是箭头从哪个对象中获取成员,而箭头运算符获取成员这个事实则永远不变。
  • 对于形如point->mem的表达式,point必须是指向类对象的指针或者是一个重载了operator->的类对象。其执行过程如下:
  • 如果point是指针,则应用内置的箭头运算符,表达式等价(*point).men;
  • 如果point是定义了operator->的类对象,则我们使用point.operator->()的结果来获取mem。其中,如果该结果是一个指针,则执行上一步;如果该结果本身含有重载的operator->,则重复调用当前步骤。


8 函数调用运算符

8-1 lambda是函数对象

  • 当我们编写一个lambda表达式后,编译器将该表达式翻译成一个未命名类的未命名对象。在lambda表达式产生的类中含有一个重载的函数调用运算符。

8-2 标准库定义的函数对象

  • 如,plus<int>另加法运算符作用于int,并返回int;less<string*>用来比较两个int指针(标准库规定指针的less是定义良好的,可能是比较两个指针的地址)。

8-3 可调用对象与function

  • C++中有几种可调用对象:函数、函数指针、lambda表达式、bind创建的可调用对象以及重载了函数调用运算符的类。
  • 可调用对象也是有类型的,两个不同类型的可调用对象可以共享同一种调用形式。调用形式指明了调用返回的类型以及传递给调用的实参类型,一种调用形式对应一个函数类型,如int(int,int)。
  • C++11定义了一种可调用对象类型function(可以代表任何相同调用形式的可调用对象)。
function的操作function<T> ff是一个用来存储可调用对象的空function,这些可调用对象的调用形式为T(即retType(args))function<T> f(nullptr)显示构造一个空functionfunction<T> f(obj)在f中存储可调用对象obj的副本f将f作为条件:f含有一个可调用对象为真;否则为假f(args)调用f中的对象,参数是args
function的成员类型result_type该function类型的可调用对象的返回值类型argument_type当T有一个或两个实参时定义的类型。如果T只有一个实参,则argument_typefirst_argument_type是该类型的同义词;如果T有两个实参,则first_argument_type和second_argument_typesecond_argument_type分别代表两个实参的类型


9 重载、类型转换与运算符

9-1 类型转换运算符

  • 类型转换运算符时类的一种特殊成员函数,它负责将一个类类型的转换成其他类型。
  • 一个类型转换函数必须是类的成员函数;它不能声明返回值类型,形参列表也必须为空。类型转换函数通常应该是const,其形式为operator type()const。
  • 尽管编译器因此只执行一个用户定义的类型转换,但是隐式的用户定义类型转换可以置于一个标准(内置)类型转换之前或之后,并与其一起使用。
  • C++11新标准引入了显示的类型转换运算符,即在operator type之前加explicit。当类型转换运算符时显式时,我们也可以执行类型转换,不过必须通过显式的强制类型转换才可以。但是,该例存在一个例外,即如果表达式被用作条件,编译器会将显式的类型转换自动应用于它。
  • 向bool类型的类型转换通常用在条件部分,所以operator bool一般定义成explicit的。

9-2 避免有二义性的类型转换

  • 如果类中包含一个或多个类型转换,则必须确保在类类型和目标类型之间只存在唯一一种转换方式,否则,很可能出现二义性错误。
  • 在两种情况下可能产生多重转换路径:
  • 第一种是两个类提供了相同的类型转换:例如A定义一个接受B类对象的转换构造函数,同时B定义了一个转换目标是A类的类型转换运算符,我们就说它们提供了相同的类型转换;
  • 第二种是类定义了多个转换规则,而这些转换涉及的类型本身可以通过其他类型转换联系到一起。最典型的就是算术运算符,对某个给定的类,最好只定义最多一个与算术类型有关的转换规则。

9-3 函数匹配与重载运算符

  • 表达式中运算符的候选函数集既应该包括成员函数,也应该包括非成员函数。



0 0