重载运算与类型转化

重载运算符的名称是operator+运算符。

重载运算符的参数个数与其作用的运算对象数量一样多。除了operator()之外其他的重载运算符都不允许有默认参数。

当一个重载运算符是成员函数时,this会被绑定到第一个参数上,因此成员函数的重载运算符会少一个参数。

不能对内置类型进行重载,但是若是同时对内置类型和非内置类型进行操作,这是可行的。

+,-,*,&四种操作同时是一元和二元。

  1. +一元表示正,二元表示加。
  2. -一元表示负,二元表示减。
  3. *一元表示解引用,二元表示乘。
  4. &一元表示取地址,二元表示与。

这些重载的运算符的优先级与结合律会和内置运算符一致。

虽然可以直接使用运算符来调用,但也可以像函数调用一样调用。要注意的是,函数的名称是有operator的,且成员函数要用点运算符来调用。

不重载的运算符

一般情况下,不应该重载逗号,取地址,逻辑与和逻辑或运算符。逗号和取地址c++已经定义了其作用于类的特殊含义。逻辑与和逻辑或无法保留求值顺序和短路求值属性。

算术运算符返回右值,赋值运算符返回左值。

使用场景

一般只有操作的含义对于使用者非常明了的时候才使用重载运算符。

成员函数与非成员函数

一般来说,

成员函数:=,[],(),→,复合赋值,递增,递减,解引用。

非成员函数:算术,相关性,关系,位运算符。

具有对称性,也就是即便左右变量互换也要成立,的函数用非成员函数。

输出运算符<<

非成员函数,由于首参不是类对象。一般要被声明为友元。

形参:

  1. ostream引用
  2. 类对象的引用常量

返回值:ostream引用

输出运算符不应该控制格式,要给予用户自由度,如不应该输出换行。

输入运算符>>

非成员函数,由于首参不是类对象。一般要被声明为友元。

形参:

  1. istream引用
  2. 类对象的引用

返回值:istream引用

相较于输出运算符不需要检查错误,输入需要检查是否发生IO错误,当发生错误时需要将读入对象设为默认状态,保证其正确性,因为在发生错误时可能已经读入了一部分了。

一般在所有数据读入完成后,数据使用之前检查istream来判断是否发生IO错误,因为我们并不在意哪一个出了问题。

即便IO没有发生错误,但读入格式或者其他出错了也可以通过标识istream的状态来标示错误。

算术运算符

非成员函数,允许对左侧和右侧对象进行转换。

形参:常量引用

返回值:局部变量的副本

一般使用复合赋值运算符来实现算术运算符,而非相反。

关系运算符(一般要const)

非成员函数,允许对左侧和右侧对象进行转换。

形参:常量引用

返回值:bool类型

相等运算符应该具有传递性。在实现上,可以return一个判断等于的&&长表达式。

一般来说实现小于运算符,其他调用即可。需要保证当两个对象不相等时一个应该小于另一个,错误的情况比如多个数据成员而小于运算符只比较其中一两个,而==运算符保证全部成员相等。

当且仅当有唯一一种逻辑可靠的小于定义且小于运算符与==运算符相符时定义小于运算符。

赋值运算符=(复合赋值运算符)

成员函数(复合赋值运算符有时候会被定义为非成员函数)。除去移动与拷贝赋值运算符,还有其他的赋值运算符。如参数时initializer_list的列表赋值。

形参:常量引用

返回值:自身的引用

下标运算符[]

成员函数。一般会定义一个常量版本和一个非常量版本。

形参:std::size_t(int)

返回值:(常量)引用

递增递减运算符

成员函数(虽然允许是非成员函数)。

前置

  • 形参:无
  • 返回值:对自身的引用

后置,一般调用前置运算符来实现

  • 形参:一个无用的int,用于区分前置和后置,由于无需用到,因此一般不用命名。
  • 返回值:自身改变前的值

成员访问运算符(const)

成员函数。

解引用(允许是非成员函数)

  • 形参:无
  • 返回值:引用

箭头(调用者:指针或重载了箭头的类对象)

  • 形参:无
  • 返回值:指针

箭头运算符不管怎么重载,只能改变其是从哪个对象中获取成员,但其获取成员这个本质属性不会变。

函数调用运算符()

成员函数。

形参:任意

返回值:任意

当一个类定义了调用运算符,那么类的成员被称为函数对象。在定义对象后可以像函数一样调用。

其与函数的最大区别就在于其能存储变量。在初始化时或是后续修改能定制它的行为。

一般作为泛型算法的实参对象。如

lambda

lambda生成了一个未命名(内部命名)的类,这个类只有一个函数,即调用运算符重载。

而对于捕获参数,如果有值传递,就相当于这个类有一个构造函数,这个构造函数的参数就是值传递的参数,初始化其中的局部变量,即便。若是引用传递,那么程序会保证lambda使用时其存在性。

其不会含有默认构造函数、赋值运算符及折构函数。但拷贝/移动构造函数不一定。

标准库中的函数对象

中定义了一些重载了调用运算符的函数对象。

虽然比较指针是不合法的,但是这些函数对象会对指针所指向的对象进行操作。

function

可调用对象包括:函数、函数指针、lambda、bind、重载了函数调用运算符的类

这些对象的类型不一而定,所以即便他们享有着同样的调用形式(即形参相同,返回值相同),他们的类型名也会不同。因此我们不能把它们放在同一个容器中,并通过轻松的方式进行选取调用,如一个计算器中几种运算分别使用了不同的可调用对象实现,但却希望放在同一个函数表中。

中function被定义。

function的类型就是调用形式(return type(param_list type) ),可以使用直接初始化也可以拷贝赋值。

当存在重载函数时,虽然其调用形式是不同的,但通过函数名是判断不出来的,因此可以通过函数指针的方式来间接特指。

类型转换

一种是通过隐式调用构造函数的方式进行类型转换。

另一种是通过定义类型转换运算符,是将自定义类型转换为另一种类型,与构造函数的转换正好相反。

形式:operator type() const;

这里的type只要是能返回的类型就是可行的。

除非两个类型之间存在明确的一一对应关系,不然最好不要定义类型转换,而是通过非成员函数从中提取信息。

另外,如果定义了一些类型转换,其可能会在不知情的情况下隐式发生,发生意想不到的情况。

因此可以通过explicit限定其必须显式执行。但若是该表达式用作条件判断,其必然会被隐式转换为bool而不管是否explicit。也正是因此,由于bool转换一般用于条件中,因此一般会explicit。

二义性

  1. 若是在两个类中都定义了互相的类型转换运算符和转换构造函数,那么当从一个类转为另一个类的时候既可以调用A的类型转换运算符也可以使用B的转换构造函数,产生了二义性。
  2. 当类定义了多个转换规则(无论是类型转换运算符还是转换构造函数),若是其转得的类型是平级的,例如double和int,那么若是其再次需要转换为另一种类型,例如说long,则会产生二义性,不知道该先转换为哪种类型。

类型转换设计很麻烦。为了避免上述的二义性,要

  1. 不要令两个类执行相同的类型转换
  2. 若是定义了目标是内置算术类型的类型转换
    1. 不要定义接受算术类型的类型转换,让这个算数类型转换为本类型再操作
    2. 不要定义多种到算术运算符的类型转换,让标准类型转换来执行这个过程

当两个重载函数的参数类型都定义了某个类型到其的转换,那么也会发生二义性。可以通过显式转换来消除二义性。但这也表示了程序的设计不足。

但即便其定义的转换类型是不同的,double和int,也会由于存在标准类型转换而使得两个重载都是可以的,产生二义性。

由于重载运算符也是重载函数,其在匹配时会和标准的运算符或者非成员函数可能会产生二义性。如A定义了与int的双向类型转换,但是,同样重载了运算符+,那么此时使A+1就会发生二义性,不知道该调用int+int还是A+A。

自定义运算符重载

格式:return_type operator"" operator_name(){}return\_type\ operator""\ operator\_name()\{\}


重载运算与类型转化
https://lhish.github.io/project/hide/重载运算与类型转化/
作者
lhy
发布于
2024年6月30日
许可协议