c++学习(二 )——重载操作符

时间:2023-01-29 17:47:33
输入输出操作符 支持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; } }; 这个结构很简单,它定义了一个操作,函数调用操作符,该操作符有一个形参并返回形参的绝对值。 通过类类型的对象提供一个实参表而使用调用操作符,所用的方式看起来像一个函数调用。 函数调用操作符必须声明为成员函数,一个类可以定义函数调用操作符的多个版本,由形参的数目或类型加以区别。 定义了调用操作符的类,其对象常称为函数对象,即它们是行为类似函数的对象。