Team LiB
Previous Section Next Section

14.2. Input and Output Operators

14.2. 输入和输出操作符

Classes that support I/O ordinarily should do so by using the same interface as defined by the iostream library for the built-in types. Thus, many classes provide overloaded instances of the input and output operators.

支持 I/O 操作的类所提供的 I/O 操作接口,一般应该与标准库 iostream 为内置类型定义的接口相同,因此,许多类都需要重载输入和输出操作符。

14.2.1. Overloading the Output Operator <<

14.2.1. 输出操作符 << 的重载

To be consistent with the IO library, the operator should take an ostream& as its first parameter and a reference to a const object of the class type as its second. The operator should return a reference to its ostream parameter.

为了与 IO 标准库一致,操作符应接受 ostream& 作为第一个形参,对类类型 const 对象的引用作为第二个形参,并返回对 ostream 形参的引用。



The general skeleton of an overloaded output operator is

重载输出操作符一般的简单定义如下:

     // general skeleton of the overloaded output operator
     ostream&
     operator <<(ostream& os, const ClassType &object)
     {
         // any special logic to prepare object

         // actual output of members
         os << // ...

         // return ostream object
         return os;
     }

The first parameter is a reference to an ostream object on which the output will be generated. The ostream is nonconst because writing to the stream changes its state. The parameter is a reference because we cannot copy an ostream object.

第一个形参是对 ostream 对象的引用,在该对象上将产生输出。ostream 为非 const,因为写入到流会改变流的状态。该形参是一个引用,因为不能复制 ostream 对象。

The second parameter ordinarily should be a const reference to the class type we want to print. The parameter is a reference to avoid copying the argument. It can be const because (ordinarily) printing an object should not change it. By making the parameter a const reference, we can use a single definition to print const and nonconst objects.

第二个形参一般应是对要输出的类类型的引用。该形参是一个引用以避免复制实参。它可以是 const,因为(一般而言)输出一个对象不应该改变对象。使形参成为 const 引用,就可以使用同一个定义来输出 const 和非 const 对象。

The return type is an ostream reference. Its value is usually the ostream object against which the output operator is applied.

返回类型是一个 ostream 引用,它的值通常是输出操作符所操作的 ostream 对象。

The Sales_item Output Operator
Sales_item 输出操作符

We can now write the Sales_item output operator:

现在可以编写 Sales_item 的输出操作符了:

     ostream&
     operator<<(ostream& out, const Sales_item& s)
     {
         out << s.isbn << "\t" << s.units_sold << "\t"
             << s.revenue << "\t" << s.avg_price();
         return out;
     }

Printing a Sales_item entails printing its three data elements and the computed average sales price. Each element is separated by a tab. After printing the values, the operator returns a reference to the ostream it just wrote.

输出 Sales_item,就需要输出它的三个数据成员以及计算得到的平均销售价格,每个成员用制表符间隔。输出值之后,该操作符返回对所写 ostream 对象的引用。

Output Operators Usually Do Minimal Formatting
输出操作符通常所做格式化尽量少

Class designers face one significant decision about output: whether and how much formatting to perform.

关于输出,类设计者面临一个重要决定:是否格式化以及进行多少格式化。

Generally, output operators should print the contents of the object, with minimal formatting. They should not print a newline.

一般而言,输出操作符应输出对象的内容,进行最小限度的格式化,它们不应该输出换行符。



The output operators for the built-in types do little if any formatting and do not print newlines. Given this treatment for the built-in types, users expect class output operators to behave similarly. By limiting the output operator to printing just the contents of the object, we let the users determine what if any additional formatting to perform. In particular, an output operator should not print a newline. If the operator does print a newline, then users would be unable to print descriptive text along with the object on the same line. By having the output operator perform minimal formatting, we let users control the details of their output.

用于内置类型的输出操作符所做格式化很少,并且不输出换行符。由于内置类型的这种既定处理,用户预期类输出操作符也有类似行为。通过限制输出操作符只输出对象的内容,如果需要执行任意额外的格式化,我们让用户决定该如何处理。尤其是,输出操作符不应该输出换行符,如果该操作符输出换行符,则用户就不能将说明文字与对象输出在同一行上。尽量减少操作符所做格式化,让用户自己控制输出细节。

IO Operators Must Be Nonmember Functions
IO 操作符必须为非成员函数

When we define an input or output operator that conforms to the conventions of the iostream library, we must make it a nonmember operator. Why?

当定义符合标准库 iostream 规范的输入或输出操作符的时候,必须使它成为非成员操作符,为什么需要这样做呢?

We cannot make the operator a member of our own class. If we did, then the left-hand operand would have to be an object of our class type:

我们不能将该操作符定义为类的成员,否则,左操作数将只能是该类类型的对象:

     // if operator<< is a member of Sales_item
     Sales_item item;
     item << cout;

This usage is the opposite of the normal way we use output operators defined for other types.

这个用法与为其他类型定义的输出操作符的正常使用方式相反。

If we want to support normal usage, then the left-hand operand must be of type ostream. That means that if the operator is to be a member of any class, it must be a member of class ostream. However, that class is part of the standard library. Weand anyone else who wants to define IO operatorscan't go adding members to a class in the library.

如果想要支持正常用法,则左操作数必须为 ostream 类型。这意味着,如果该操作符是类的成员,则它必须是 ostream 类的成员,然而,ostream 类是标准库的组成部分,我们(以及任何想要定义 IO 操作符的人)是不能为标准库中的类增加成员的。

Instead, if we want to use the overloaded operators to do IO for our types, we must define them as a nonmember functions. IO operators usually read or write the nonpublic data members. As a consequence, classes often make the IO operators friends.

相反,如果想要使用重载操作符为该类型提供 IO 操作,就必须将它们定义为非成员函数。IO 操作符通常对非公用数据成员进行读写,因此,类通常将 IO 操作符设为友元。

Exercises Section 14.2.1

Exercise 14.7:

Define an output operator for the following CheckoutRecord class:

为下面的 CheckoutRecord 类定义一个输出操作符:

     class CheckoutRecord {
     public:
         // ...
     private:
         double book_id;
         string title;
         Date date_borrowed;
         Date date_due;
         pair<string,string> borrower;
         vector< pair<string,string>* > wait_list;
     };

Exercise 14.8:

In the exercises to Section 12.4 (p. 451) you wrote a sketch of one of the following classes:

第 12.4 节的习题中,你编写了下面某个类的框架:

     (a) Book     (b) Date     (c) Employee
     (d) Vehicle  (e) Object   (f) Tree

Write the output operator for the class you chose.

为所选择的类编写输出操作符。


14.2.2. Overloading the Input Operator >>

14.2.2. 输入操作符 >> 的重载

Similar to the output operator, the input operator takes a first parameter that is a reference to the stream from which it is to read, and returns a reference to that same stream. Its second parameter is a nonconst reference to the object into which to read. The second parameter must be nonconst because the purpose of an input operator is to read data into this object.

与输出操作符类似,输入操作符的第一个形参是一个引用,指向它要读的流,并且返回的也是对同一个流的引用。它的第二个形参是对要读入的对象的非 const 引用,该形参必须为非 const,因为输入操作符的目的是将数据读到这个对象中。

A more important, and less obvious, difference between input and output operators is that input operators must deal with the possibility of errors and end-of-file.

更重要但通常重视不够的是,输入和输出操作符有如下区别:输入操作符必须处理错误和文件结束的可能性。



The Sales_item Input Operator
Sales_item 的输入操作符

The Sales_item input operator looks like:

Sales_item 的输入操作符如下:

     istream&
     operator>>(istream& in, Sales_item& s)
     {
         double price;
         in >> s.isbn >> s.units_sold >> price;
         // check that the inputs succeeded
         if (in)
            s.revenue = s.units_sold * price;
         else
            s = Sales_item(); // input failed: reset object to default state
         return in;
     }

This operator reads three values from its istream parameter: a string value, which it stores in the isbn member of its Sales_item parameter; an unsigned, which it stores in the units_sold member; and a double, which it stores in a local named price. Assuming the reads succeed, the operator uses price and units_sold to set the object's revenue member.

这个操作符从 istream 形参中读取三个值:一个 string 值,存储到 isbn 成员中;一个 unsigned 值,存储到 Sales_item 形参的 units_sold 成员中;一个 double 值,存储到 Sales_item 形参的 price 成员中。假定读取成功,操作符用 priceunits_sold 来设置 Sales_item 对象的 revenue 成员。

Errors During Input
输入期间的错误

Our Sales_item input operator reads the expected values and checks whether an error occurred. The kinds of errors that might happen include:

Sales_item 的输入操作符将读入所期望的值并检查是否发生错误。可能发生的错误包括如下种类:

  1. Any of the read operations could fail because an incorrect value was provided. For example, after reading isbn, the input operator assumes that the next two items will be numeric data. If nonnumeric data is input, that read and any subsequent use of the stream will fail.

    任何读操作都可能因为提供的值不正确而失败。例如,读入 isbn 之后,输入操作符将期望下两项是数值型数据。如果输入非数值型数据,这次的读入以及流的后续使用都将失败。

  2. Any of the reads could hit end-of-file or some other error on the input stream.

    任何读入都可能碰到输入流中的文件结束或其他一些错误。

Rather than checking each read, we check once before using the data we read:

我们无需检查每次读入,只在使用读入数据之前检查一次即可:

     // check that the inputs succeeded
     if (in)
         s.revenue = s.units_sold * price;
     else
         s = Sales_item(); // input failed: reset object to default state

If one of the reads failed, then price would be uninitialized. Hence, before using price, we check that the input stream is still valid. If it is, we do the calculation and store it in revenue. If there was an error, we do not worry about which input failed. Instead, we reset the entire object as if it were an empty Sales_item. We do so by creating a new, unnamed Sales_item constructed using the default constructor and assigning that value to s. After this assignment, s will have an empty string for its isbn member, and its revenue and units_sold members will be zero.

如果这些读入有一个失败了,则 price 可能没有初始化。因此,在使用 price 之前,我们需要检查输入流是否仍有效。如果有效,就进行计算并将结果存储到 revenue 中;如果出现了错误,我们不用关心是哪个输入失败了,相反,我们将整个对象复位,就好像它是一个空 Sales_item 对象,具体做法是创建一个新的、未命名的、用默认构造的 Sales_item 对象并将它赋值给 s。赋值之后,sisbn 成员是一个空 string,它的 revenueunits_sold 成员为 0。

Handling Input Errors
处理输入错误

If an input operator detects that the input failed, it is often a good idea to make sure that the object is in a usable and consistent state. Doing so is particularly important if the object might have been partially written before the error occurred.

如果输入操作符检测到输入失败了,则确保对象处于可用和一致的状态是个好做法。如果对象在错误发生之前已经写入了部分信息,这样做就特别重要。

For example, in the Sales_item input operator, we might successfully read a new isbn, and then encounter an error on the stream. An error after reading isbn would mean that the units_sold and revenue members of the old object were unchanged. The effect would be to associate a different isbn with that data.

例如,在 Sales_item 的输入操作符中,可能成功地读入了一个新的 isbn,然后遇到流错误。在读入 isbn 之后发生错误意味着旧对象的 units_soldrevenue 成员没变,结果会将另一个 isbn 与那个数据关联。

In this operator, we avoid giving the parameter an invalid state by resetting it to the empty Sales_item if an error occurs. A user who needs to know whether the input succeeded can test the stream. If the user ignores the possibility of an input error, the object is in a usable stateits members are all all defined. Similarly, the object won't generate misleading resultsits data are internally consistent.

在这个操作符中,如果发生了错误,就将形参恢复为空 Sales_item 对象,以避免给它一个无效状态。用户如果需要输入是否成功,可以测试流。即使用户忽略了输入可能错误,对象仍处于可用状态——它的成员都已经定义。类似地,对象将不会产生令人误解的结果——它的数据是内在一致的。

When designing an input operator, it is important to decide what to do about error-recovery, if anything.

设计输入操作符时,如果可能,要确定错误恢复措施,这很重要。



Indicating Errors
指出错误

In addition to handling any errors that might occur, an input operator might need to set the condition state (Section 8.2, p. 287) of its input istream parameter. Our input operator is quite simplethe only errors we care about are those that could happen during the reads. If the reads succeed, then our input operator is correct and has no need to do additional checking.

除了处理可能发生的任何错误之外,输入操作符还可能需要设置输入形参的条件状态(第 8.2 节)。我们的输入操作符相当简单——我们只关心读入期间可能发生的错误。如果读入成功,则输入操作符就是正确的而且不需要进行附加检查。

Some input operators do need to do additional checking. For example, our input operator might check that the isbn we read is in an appropriate format. We might have read data successfully, but these data might not be suitable when interpreted as an ISBN. In such cases, the input operator might need to set the condition state to indicate failure, even though technically speaking the actual IO was successful. Usually an input operator needs to set only the failbit. Setting eofbit would imply that the file was exhausted, and setting badbit would indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.

有些输入操作符的确需要进行附加检查。例如,我们的输入操作符可以检查读到的 isbn 格式是否恰当。也许我们已成功读取了数据,但这些数据不能恰当解释为 ISBN,在这种情况下,尽管从技术上说实际的 IO 是成功的,但输入操作符仍可能需要设置条件状态以指出失败。通常输入操作符仅需设置 failbit。设置 eofbit 意思是文件耗尽,设置 badbit 可以指出流被破坏,这些错误最好留给 IO 标准库自己来指出。

Exercises Section 14.2.2

Exercise 14.9:

Describe the behavior of the Sales_item input operator if given the following input:

给定下述输入,描述 Sales_item 输入操作符的行为。

     (a) 0-201-99999-9 10 24.95
     (b) 10 24.95 0-210-99999-9

Exercise 14.10:

What is wrong with the following Sales_item input operator?

下述 Sales_item 输入操作符有什么错误?

     istream& operator>>(istream& in, Sales_item& s)
     {
         double price;
         in >> s.isbn >> s.units_sold >> price;
         s.revenue = s.units_sold * price;
         return in;
     }

What would happen if we gave this operator the data in the previous exercise?

如果将上题中的数据作为输入,将会发生什么?

Exercise 14.11:

Define an input operator for the CheckoutRecord class defined in the exercises for Section 14.2.1 (p. 515). Be sure the operator handles input errors.

第 14.2.1 节习题中定义的 CheckoutRecord 类定义一个输入操作符,确保该操作符处理输入错误。


Team LiB
Previous Section Next Section