c++学习(二 )——重载操作符
输入输出操作符
支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同。因此,许多类都需要重载输入和输出操作符。
输出操作符<<的重载
为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回对ostream形参的引用。
重载输出操作符一般的简单定义如下:
// general skeleton of the overloaded ouput operator
Ostream& operator <<(ostream& os, const ClassType &object)
{
// any special logic to prepare object
// actual ouput of members
os << //…
// return ostream object
return os;
}
第一个形参是对ostream对象的引用,在该对象上产生输出,ostream为非const,因为下入到流会改变流的状态。该形参是一个引用,因为不能复制ostream对象。
第二个对象一般应是对要输出的类类型的引用。该形参是一个引用以避免复制实参。它可以是const,因为输出一个对象不应该改变该对象。
返回类型是一个ostream引用,它的值通常是输出操作符所操作的ostream对象。
输出操作符通常所做格式化应尽量少。
关于输出,累设计者面临着一个重要决定:是否格式化以及进行多少格式化。
一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,他们不应该输出换行符。
用于内置类型的输出操作符所作的格式化很少,并且不输出换行符。通过限制输出操作符只输出对象的内容,如果需要执行任意额外的格式化,我们让用户决定该如何处理。尤其是,输出操作符不应该输出换行符。尽量减少操作符所作的格式化,让用户自己控制输出细节。
IO操作符必须为非成员函数
当定义符合标准库iostream规范的输入或输出操作符的时候,必须使它成为非成员操作符。
我们不能将该操作符定义为类的成员,否则,左操作数将只能是该类类型的对象。
// if operator << is a member of Sales_item
Sales_item item;
item << cout;
这个用法与为其他类型定义的输出操作符的正常使用方式相反。如果想要支持正常的用法,则左操作数必须为ostream类型。如果该操作符是类的成员,则它必须是ostream类的成员,然而ostream类是标准库的组成部分,我们是不能为标准库的类增加成员的。
如果想要使用重载操作符为该类型提供IO操作,就必须将他们定义为非成员函数。IO操作符通常对非公用数据成员进行读写,因此,类通常将IO操作符设为友元。
输入操作符>>的重载
与输出操作符类似:输入操作符的第一个形参是一个引用,指向它要读的流,并且返回的也是对同一个流的引用。它的第二个形参是对要读入的对象的非const引用,该形参必须为非const,因为输入操作符的目的是将数据读到这个对象中。
ostream& operator >>(ostream& os, ClassType &object)
需要注意的是:输入和输出操作符有如下区别,输入操作符必须处理错误和文件结束的可能性。
如何处理输入错误
如果输入操作符检测到输入失败了,则确保对象处于可用和一致的状态是个好做法。如果对象在错误发生之前已经写入了部分欣喜,这样做就特别重要。
如果发生了错误,就将形参恢复为空对象,以避免给它一个无效的状态。用户如果需要知道输入是否成功,可以测试流。即使用户忽略了输入可能错误,对象仍处于可用状态——他的成员已经定义。
设计输入操作符时,如果可能,要确定错误恢复措施,这很重要。
指出错误
除了处理可能发生的任何错误之外,输入操作符还可能需要设置输入形参的条件状态。我们的输入操作符相当简单——我们只关心读入期间可能发生的错误。
算术操作符和关系操作符
一般而言,将算术和关系操作符定义为非成员函数。为了与内置操作符保持一致,加法返回一个右值,而不是一个引用
例:
// assumes that both objects refer to the same isbn
Sales_item operator +(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs);
ret += rhs;
return ret; // ret 因该理解为左值还是右值呢?概念有所混淆 我觉得返回的是左值
}
算术操作符通常产生一个新值,该值是两个操作数的计算结果,它不同于任一操作数且在一个局部变量中计算,返回对那个变量的引用是一个运行时的错误。
既定义了算术操作符又定义了相关复合赋值操作符的类,一般应使用复合赋值实现算术操作符。
赋值操作符
类赋值操作符接受类类型的形参,通常,该形参是对类类型的const引用,但也可以是类类型或对类类型的非const 引用。如果没有定义这个操作符,编译器就会合成它。类赋值操作符必须是类的成员,以便编译器可以知道是否需要合成一个。
赋值操作符可以重载。无论形参为何种类型,赋值操作符必须定义为成员函数。
赋值必须返回对*this的引用。
因为赋值返回的是一个引用,就不需要创建和撤销结果的临时副本。返回值通常是左操作数的引用。
注意:一般而言,赋值操作符与复合赋值操作符应返回左操作数的引用。
下标操作符
下标操作符必须定义为类成员函数。
提供读写访问
定义下标操作符比较复杂的地方在于,它在用作赋值的左右操作数时都应该能表现正常,下标操作符出现在左边,必须生成左值,可以指定引用作为返回类型而得到左值。只要下标操作符返回引用,就可用作赋值的任意一方。
类定义下标操作符的时候,一般需要定义两个版本:一个为非const成员并返回引用,另一个为const成员并返回const引用。
自增操作符和自减操作符
定义自增/自减操作符
C++语言不要求自增操作符或自减操作符一定作为类的成员,但是,因为这些操作符改变操作对象的状态。但是,因为这些操作符改变操作对象的状态,所以更倾向于将他们作为成员函数。
定义前自减/前自增操作符
class CheckedPtr
{
public:
CheckedPtr& operator++();
CheckedPtr& operator—();
};
为了与内置类型一致,前缀式操作符应返回被增量或减量对象的引用。
区别操作符的前缀和后缀形式
前缀式和后缀式存在一个问题:它们的形参数目和类型相同
为了解决这个问题,后缀式操作符函数接受一个额外的int型形参。使用后缀式操作符时,编译器提供0座位这个形参的实参。
定义后缀式操作符
class CheckedPtr
{
public:
CheckedPtr operator ++ (int);
CheckedPtr operator – (int);
};
为了与内置操作符一致,后缀式操作符应返回旧值(尚未自增或自减的值),并且应作为值返回,而不是返回应用。
一般而言,最好前缀式和后缀式都定义。只定义前缀式或只定义后缀式的类,将会让习惯于使用两种形式的用户感到奇怪。
调用操作符和函数对象
可以为类类型的对象重载函数调用操作符。一般为表示操作的类重载调用操作符。
例如,可以定义名为absInt的结构,该结构将int类型的值转换为绝对值得操作:
class absInt
{
int operator()(int val)
{
Return val < 0 ? –val: val;
}
};
这个结构很简单,它定义了一个操作,函数调用操作符,该操作符有一个形参并返回形参的绝对值。
通过类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用。
函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。
定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。