重载运算与类型转化
重载运算符的名称是operator+运算符。
重载运算符的参数个数与其作用的运算对象数量一样多。除了operator()之外其他的重载运算符都不允许有默认参数。
当一个重载运算符是成员函数时,this会被绑定到第一个参数上,因此成员函数的重载运算符会少一个参数。
不能对内置类型进行重载,但是若是同时对内置类型和非内置类型进行操作,这是可行的。
+,-,*,&四种操作同时是一元和二元。
- +一元表示正,二元表示加。
- -一元表示负,二元表示减。
- *一元表示解引用,二元表示乘。
- &一元表示取地址,二元表示与。
这些重载的运算符的优先级与结合律会和内置运算符一致。
虽然可以直接使用运算符来调用,但也可以像函数调用一样调用。要注意的是,函数的名称是有operator的,且成员函数要用点运算符来调用。
不重载的运算符
一般情况下,不应该重载逗号,取地址,逻辑与和逻辑或运算符。逗号和取地址c++已经定义了其作用于类的特殊含义。逻辑与和逻辑或无法保留求值顺序和短路求值属性。
算术运算符返回右值,赋值运算符返回左值。
使用场景
一般只有操作的含义对于使用者非常明了的时候才使用重载运算符。
成员函数与非成员函数
一般来说,
成员函数:=,[],(),→,复合赋值,递增,递减,解引用。
非成员函数:算术,相关性,关系,位运算符。
具有对称性,也就是即便左右变量互换也要成立,的函数用非成员函数。
输出运算符<<
非成员函数,由于首参不是类对象。一般要被声明为友元。
形参:
- ostream引用
- 类对象的引用常量
返回值:ostream引用
输出运算符不应该控制格式,要给予用户自由度,如不应该输出换行。
输入运算符>>
非成员函数,由于首参不是类对象。一般要被声明为友元。
形参:
- istream引用
- 类对象的引用
返回值:istream引用
相较于输出运算符不需要检查错误,输入需要检查是否发生IO错误,当发生错误时需要将读入对象设为默认状态,保证其正确性,因为在发生错误时可能已经读入了一部分了。
一般在所有数据读入完成后,数据使用之前检查istream来判断是否发生IO错误,因为我们并不在意哪一个出了问题。
即便IO没有发生错误,但读入格式或者其他出错了也可以通过标识istream的状态来标示错误。
算术运算符
非成员函数,允许对左侧和右侧对象进行转换。
形参:常量引用
返回值:局部变量的副本
一般使用复合赋值运算符来实现算术运算符,而非相反。
关系运算符(一般要const)
非成员函数,允许对左侧和右侧对象进行转换。
形参:常量引用
返回值:bool类型
相等运算符应该具有传递性。在实现上,可以return一个判断等于的&&长表达式。
一般来说实现小于运算符,其他调用即可。需要保证当两个对象不相等时一个应该小于另一个,错误的情况比如多个数据成员而小于运算符只比较其中一两个,而==运算符保证全部成员相等。
当且仅当有唯一一种逻辑可靠的小于定义且小于运算符与==运算符相符时定义小于运算符。
赋值运算符=(复合赋值运算符)
成员函数(复合赋值运算符有时候会被定义为非成员函数)。除去移动与拷贝赋值运算符,还有其他的赋值运算符。如参数时initializer_list的列表赋值。
形参:常量引用
返回值:自身的引用
下标运算符[]
成员函数。一般会定义一个常量版本和一个非常量版本。
形参:std::size_t(int)
返回值:(常量)引用
递增递减运算符
成员函数(虽然允许是非成员函数)。
前置
- 形参:无
- 返回值:对自身的引用
后置,一般调用前置运算符来实现
- 形参:一个无用的int,用于区分前置和后置,由于无需用到,因此一般不用命名。
- 返回值:自身改变前的值
成员访问运算符(const)
成员函数。
解引用(允许是非成员函数)
- 形参:无
- 返回值:引用
箭头(调用者:指针或重载了箭头的类对象)
- 形参:无
- 返回值:指针
箭头运算符不管怎么重载,只能改变其是从哪个对象中获取成员,但其获取成员这个本质属性不会变。
函数调用运算符()
成员函数。
形参:任意
返回值:任意
当一个类定义了调用运算符,那么类的成员被称为函数对象。在定义对象后可以像函数一样调用。
其与函数的最大区别就在于其能存储变量。在初始化时或是后续修改能定制它的行为。
一般作为泛型算法的实参对象。如
lambda
lambda生成了一个未命名(内部命名)的类,这个类只有一个函数,即调用运算符重载。
而对于捕获参数,如果有值传递,就相当于这个类有一个构造函数,这个构造函数的参数就是值传递的参数,初始化其中的局部变量,即便。若是引用传递,那么程序会保证lambda使用时其存在性。
其不会含有默认构造函数、赋值运算符及折构函数。但拷贝/移动构造函数不一定。
标准库中的函数对象
在
虽然比较指针是不合法的,但是这些函数对象会对指针所指向的对象进行操作。
function
可调用对象包括:函数、函数指针、lambda、bind、重载了函数调用运算符的类
这些对象的类型不一而定,所以即便他们享有着同样的调用形式(即形参相同,返回值相同),他们的类型名也会不同。因此我们不能把它们放在同一个容器中,并通过轻松的方式进行选取调用,如一个计算器中几种运算分别使用了不同的可调用对象实现,但却希望放在同一个函数表中。
在
function的类型就是调用形式(return type(param_list type) ),可以使用直接初始化也可以拷贝赋值。
当存在重载函数时,虽然其调用形式是不同的,但通过函数名是判断不出来的,因此可以通过函数指针的方式来间接特指。
类型转换
一种是通过隐式调用构造函数的方式进行类型转换。
另一种是通过定义类型转换运算符,是将自定义类型转换为另一种类型,与构造函数的转换正好相反。
形式:operator type() const;
这里的type只要是能返回的类型就是可行的。
除非两个类型之间存在明确的一一对应关系,不然最好不要定义类型转换,而是通过非成员函数从中提取信息。
另外,如果定义了一些类型转换,其可能会在不知情的情况下隐式发生,发生意想不到的情况。
因此可以通过explicit限定其必须显式执行。但若是该表达式用作条件判断,其必然会被隐式转换为bool而不管是否explicit。也正是因此,由于bool转换一般用于条件中,因此一般会explicit。
二义性
- 若是在两个类中都定义了互相的类型转换运算符和转换构造函数,那么当从一个类转为另一个类的时候既可以调用A的类型转换运算符也可以使用B的转换构造函数,产生了二义性。
- 当类定义了多个转换规则(无论是类型转换运算符还是转换构造函数),若是其转得的类型是平级的,例如double和int,那么若是其再次需要转换为另一种类型,例如说long,则会产生二义性,不知道该先转换为哪种类型。
类型转换设计很麻烦。为了避免上述的二义性,要
- 不要令两个类执行相同的类型转换
- 若是定义了目标是内置算术类型的类型转换
- 不要定义接受算术类型的类型转换,让这个算数类型转换为本类型再操作
- 不要定义多种到算术运算符的类型转换,让标准类型转换来执行这个过程
当两个重载函数的参数类型都定义了某个类型到其的转换,那么也会发生二义性。可以通过显式转换来消除二义性。但这也表示了程序的设计不足。
但即便其定义的转换类型是不同的,double和int,也会由于存在标准类型转换而使得两个重载都是可以的,产生二义性。
由于重载运算符也是重载函数,其在匹配时会和标准的运算符或者非成员函数可能会产生二义性。如A定义了与int的双向类型转换,但是,同样重载了运算符+,那么此时使A+1就会发生二义性,不知道该调用int+int还是A+A。
自定义运算符重载
格式: