第14章 重载操作符与转换
C++允许重定义操作符 用于类类型对象时的含义,可以像内置转换那样使用 类类型转换,将一个类型的对象隐式转换到另一类型
14.1 重载操作符的定义
重载操作符 是 具有特殊名称的函数 ,保留字 operator后 需接定义的 操作符符号,像任意其他函数一样,重载操作符具有返回类型 和 形参表
重载操作符 必须 具有 至少一个 类类型或枚举类型的 操作数,不能重新定义用于内置类型对象的操作符的含义
大多数重载操作符可以定义为普通非成员函数 或 类的成员函数:作为类成员的重载函数,其形参看起来比操作数数目少1,作为成员函数的操作符 有一个隐含的this形参,限定为第一个操作数;操作符定义为非成员函数时,通常必须将它们设置为所操作类的友元
14.1.1 重载操作符的设计
1、不要重载具有内置含义的操作符:赋值操作符 、取地址操作符 和 逗号操作符 对类类型操作数有 默认含义,内置逻辑与和逻辑或操作符使用短路求值,重定义该操作符,将失去操作符的短路求值特征
2、大多数操作符 对类对象 没有意义(have no meaning):除非提供重载定义,赋值、取地址和逗号操作符对于类类型操作数没有意义,设计类的时候,应该确定要支持哪些操作
3、复合赋值操作符:如果一个类有 算术操作符 或 位操作符,提供相应的 复合赋值操作符 是个好的做法
4、相等和关系操作符:用作 关联容器键类型 的类,应定义 < 操作符,关联容器默认使用键类型的 < 操作符,即使该类型只存储在顺序容器中,类通常也应该定义相等操作符和小于操作符
5、选择成员或非成员实现:赋值、下标、调用、成员访问、复合赋值、自增、自减和解引用 通常应定义为类成员;对称的操作,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数
14.2 输入和输出操作符
支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同
14.2.1 输出操作符<<的重载
为了与I/O标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回ostream形参的引用
14.2.1 输出操作符>>的重载
输入操作符的第二个形参是对要读入的对象的非const引用,输入操作符必须处理错误和文件结束的可能性,设计输入操作符时,如果可能,要确定错误恢复措施
14.3 算术操作符和关系操作符
算术和关系操作符定义为 非成员函数,算术操作符 并不改变操作数的状态,操作数是对const对象的引用,产生并 返回 一个 新的对象,该对象是左操作数的副本,使用复合赋值操作符加入右操作数的值,返回的是一个右值,而不是引用
14.4 赋值操作符
类类型对象对同类型其他对象的赋值,类赋值操作符,接受类类型形参(通常是类类型的const引用,但可以是类类型或类类型的非const引用 )
赋值操作符必须是类的成员,这与复合赋值操作符有所不同
一般而言,赋值操作符 与 复合赋值操作符,应返回 左操作数的 引用
14.7 自增操作符和自减操作符
C++不要求 自增操作符或自减操作符 一定作为类的成员,因为这些操作改变操作对象,所以 更倾向于将它们作为成员;自增和自减操作符经常由诸如迭代器的类实现,这样的类提供类似于指针的行为来访问序列中的元素;前缀式操作符应返回被增量或减量的引用,后缀式应返回值,而不是引用,一般而言前缀式和后缀式都定义
14.8 调用操作符和函数对象
定义了调用操作符 的类,其对象通常称为 函数对象,即它们是行为类似函数的对象,函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别
#include <iostream> using namespace std; struct absInt{ int operator()( int val ){ //重载调用操作符 return val < 0 ? -val : val; //返回绝对值 } }; int main() { absInt absObj; //absObj是类对象,absInt类重载了调用操作符,称为函数对象 cout << absObj(-5 ); //absObj是对象,不是函数 return 0; }
#include <iostream> using namespace std; struct AddInt{ int operator()( int val1, int val2 ){ return val1 + val2; } }; int main() { AddInt add; cout << add(5,6 ); return 0; }
14.8.1 将函数对象用于标准库算法
函数对象经常用作通用算法的实参,函数对象可以比函数更灵活
14.8.2 标准库定义的函数对象
标准库定义了一组算术、关系与逻辑函数对象类,标准库还定义了一组函数适配器,能够特化或扩展标准库所定义的以及自定义的函数对象类,标准库对象类型是在functional头文件中定义的
在算法中使用标准库函数对象:sort为了按降序对容器进行排序,可以传递函数对象greater
函数对象 与 类类型的隐式转换
#include <iostream> using namespace std; #include <functional> //标准库函数对象类头文件 class A{ /* friend ostream& operator << (ostream& o, const A& a){ o << a.i << endl; return o; } */ friend int operator + (const A& a1, const A& a2){ //返回的是int return (a1.i + a2.i); //不是类类型 } /* friend A operator + (const A& a1, const A& a2){ return A(a1.i + a2.i); } */ int i; public: A():i(100){ } A(int i):i(i){ } //explicit // int 隐式转换为 A operator int(){ // A 隐式转换为 int return i; } }; int main() { plus<A> p; // 函数对象类 // 模板类型是类类型,需要普通运算符( + )重载: // 返回类型为该类类型,参数为两个 该类类型的const引用 cout << p( A(200),A(200) ); //函数对象返回 类类型 A //1、重载运算符+ 返回的是int, 要调用隐式转换 转为 类类型A (加上explicit就可以测试出来) // 重载运算符+ 返回的是 A , 就不会调用隐式转换,加上explicit也可以 //2、cout 输出: A隐式转换为 int,或重载运算符 << return 0; }
14.8.3 函数对象的函数适配器
函数适配器,特化和扩展一元和二元函数对象,函数适配器分为如下两类:
绑定器,是一种适配器,它通过将一个操作数绑定到给定值而将二元函数对象转换为一元函数对象,标准库定义了两个绑定适配器:bind1st 和 bind2st,每个绑定器接受一个函数对象和一个值;
求反器,是只用适配器,它将谓词函数对象的真值求反,标准库定义了两个求反器:not1 和 not2,not1将一元函数对象的真值求反,not2将二元函数对象的真值求反
#include <iostream> using namespace std; #include <functional> //not1 not2 bind1st bind2st //less_equal //Function object class for less-than-or-equal-to comparison (class template) #include <vector> //vector #include <algorithm> //count_if int main() { vector<int> vec = {1,2,3,4,5 }; cout << //count_if(vec.begin(), vec.end(), not1( bind2nd(less_equal<int>(), 2) ) ); //小于等于2的值求反 count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 2) ); //小于等于2 return 0; }
14.9 转换与类类型
到类类型的转换:当提供了实参类型的对象而需要一个类类型的对象时,调用非explicit 构造函数,进行隐式转换;从类类型的转换:定义 转换操作符,给定类类型的对象,该操作符将产生其他类型的对象
转换操作符 (conversion operator) 是一种特殊的类成员函数,它定义,将 类类型值 转变为 其他类型值 的转换(从类类型的转换),转换函数通用形式:operator type (); ,转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空,type 表示 内置类型名、类类型名 或由 类型别名 所定义的名字,转换为指针类型(数据或函数指针)以及引用类型 是可以的