[C++学习笔记] 运算符重载与类型转换

重载运算符

重载的运算符是具有特殊名字的函数 它们的名字由关键字operator和要定义的运算符号共同组成

重载运算符的参数数量与该运算符作用的运算对象数量一样多
重载运算符作为成员函数时 其左侧运算对象被隐式地绑定到当前调用它的对象上 显式的形参减少一个

不能重新定义内置类型对象的运算符
因此重载运算符要么是类的成员函数 要么至少有一个类类型的参数

也不能定义新的运算符 而只能重载已有的

部分运算符不能被重载: :: .* . ?:

通常情况下 也不应该重载 逗号 取地址 逻辑与 逻辑或运算符
因为它们有着内置的求值顺序或含义

可以通过运算符号间接调用运算符函数 也可以像任何其他函数一样显式调用

data1 + data2; 
operator+ (data1, data2); 
data1.operator+=(data2);

部分运算符必须是成员函数: = [] () -> 以及 类型转换运算符 operator type( ) const;
类型转换运算符不能声明返回类型(默认为定义的type) 形参列表也必须为空

由于向bool类型的转换通常用在条件部分 因此operator bool()一般定义为explicit(其他情况不能隐式调用)

改变对象状态的运算符如++,--,解引用以及复合赋值运算符(如+=,-=,/=等等)一般来说是成员函数

输入输出运算符

输入输出运算符必须是非成员函数 并且通常为类的友元函数(为了打印非公有成员)

**具有对称性的运算符(如算术和关系运算符)**一般不设置为非成员的普通函数
因为具有对称性的运算符可能转换任意一端的运算对象 例如int + doubledouble + int 都应该是成立的

输出运算符<< 的第一个形参是一个要写入的非常量的ostream对象的引用
非常量是因为我们要写入 而引用则是因为无法复制一个流对象

第二个形参一般是一个我们希望打印的对象类型的常量引用 返回值为传入的ostream形参
函数体内使用传入的形参完成输出后返回

输入运算符>>与输出运算符类似 第一个形参为要读取的流的引用 第二个形参为要写入对象类型的非常量引用
返回值为流引用

注意 输入运算符必须处理可能失败的情况 而输出不需要 当读取操作发生错误时 输入运算符应该负责从错误中恢复(如 重置为空)

赋值运算符

如果类同时定义了算术运算符和对应的复合赋值运算符 通常应该使用对应复合赋值运算符来实现算术运算符
因为operator+需要创建计算结果的新对象 operator+=只使用一个对象。
调用operator+=作为用户重载的运算符,更简洁,更符合实际逻辑。

除了拷贝赋值移动赋值运算符外 还有列表初始化赋值运算符(这三者事实上是同名不同参的重载运算符)

列表初始化赋值运算符:

strvec& operator=(std::initializer_list<std::string>);

下标运算符通常以所访问的元素的引用作为返回值
出于安全考虑 最好同时定义下标运算符的常量版本和非常量版本
在常量版本中保证对常量对象的访问返回的是常量引用 以确保不会为常量赋值

自增自减运算符

定义递增和递减运算符时应该同时定义前置版本后置版本

为了区分前置版本和后置版本 后置版本接受一个额外的不被使用的int类型的形参
其唯一作用就是为了区分 由于不会使用该形参 甚至不需要为其命名

所以当显式调用后置版本运算符时 必须传入一个任意整数参数来区分使用版本 这个参数本身不被使用

前置版本需要检查越界问题 后置版本直接调用前置版本并返回原值即可

箭头运算符

重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象
使用箭头运算符时将会重复调用结果的箭头运算符直至最后为内置箭头运算符结果或类的指针
运算符 -> 的重载比较特别,它只能是非静态的成员函数形式,而且没有参数。

如果返回值是一个原始指针,那么就将运算符的右操作数当作这个原始指针所指向类型的成员进行访问;

如果返回值是另一个类型的实例,那么就继续调用这个返回类型的 operator->() ,直到有一个调用返回一个原始指针为止,然后按第一种情况处理。

如果上述条件不满足(如:右操作数不是返回的原始指针指向的类型的成员,或者,返回的非指针类型没有重载 operator->()),那么编译将报错。

函数对象模板

如果类重载了调用运算符() 则我们可以像使用函数一样使用该类的对象
我们将这种定义了调用运算符的类的对象称之为 函数对象

函数对象常常作为泛型算法的实参 lambda是一种函数对象
通过值捕获的变量将被保存到类内的数据成员中(通过合成构造函数初始化)
而引用捕获的则不会

标准库定义了一组对应算术 关系 和逻辑运算符的函数对象模板类 可用于构造对应类型的函数对象

Plus<int> intAdd; 
int sum = intAdd(10, 20); 

我们自定义的比较中形参是没有相关性的 因此可能会产生错误 而使用标准库定义的则不会
例如比较指针地址

vector<string *> table;
// 错误:table中的指针彼此之间没有关系,将产生未定义的行为
sort(table.begin(),table.begin(),[](string *a, string *b){return a<b;});
// 正确:标准库中的less对指针的定义是良好的
sort(table.begin(),table.begin(),less<string *>());

通过function<T>模板可以使多个函数共享一个调用形式
例如多种整数运算都是int(int,int)形式

二义性问题

如果我们对同一个类既提供了转换目标是算术类型的类型转换 也提供了重载的运算符 则会遇到重载运算符与内置运算符的二义性问题

例如:对a类定义了转换到int的类型转换 又定义了与int的加法重载符 则使用a类对象与int相加时会产生二义性
无法判断是进行类型转换在使用int加法 还是使用重载的加法