重载输出运算符<<
通常情况,输入运算符的第一个形参是一个非常量ostream对象的引用。之所以ostream是非常量是因为向流写入内容会改变其状态;而该形参的引用是因为我们无法直接复制一个ostream对象。
第二个形参一般来说是一个常量的引用,该常量是我们想要打印的类类型。第二个形参是引用的原因是我们希望避免复制实参;而之所以该形参可以是常量是因为打印对象不会改变对象的内容。
为了与其他输出运算符保持一致,operator<<一般要返回它的ostream形参。
Sales_data的输出运算符
ostream& operator<<(ostream& os, const Sales_data& item) {
os << item.isbn() << " " << item.units_sold << " " << item.revenue
<< " " << item.avg_price();
return os;
}
输出运算符尽量减少格式化操作,令输出运算符减少格式化操作可以使用户有权控制输出的细节。
输入输出运算符必须是非成员函数
假设输入输出运算符是某个类的成员,则它们也必须是istream或ostream的成员。然而,这两个类属于标准库,并且我们无法给标准库中的类添加任何成员。
因此,如果我们希望为类自定义IO运算符,则必须将其定义成非成员函数。当然,IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般被声明为友元。
你在13.5节的练习中曾经编写了一个string类,为它定义了一个输出运算符
class String {
public:
String();
String(const char* str);
friend ostream& operator<<(ostream& os, const String& str);
private:
char* str;
};
ostream& operator<<(ostream& os, const String& str) {
os << str;
return os;
}
重载输入运算符>>
输入运算符的第一个形参是运算符将要读取的流的引用,第二个形参是将要读入到的对象(非常量)的引用。该运算符通常会返回某个给定流的引用。第二个形参之所以必须是个非常量是因为输入运算符本身的目的就是将数据读入到这个对象中。
istream& operator>>(istream& is, Sales_data& item) {
double price; //不需要初始化,因为我们将先读入数据到price,之后才使用它
is >> item.bookNo >> item.units_sold >> price;
if (is) //检查输入是否成功
item.revenue = item.units_sold * price;
else
item = Sales_data(); //输入失败:对象被赋予默认的状态
return is;
}
if语句检查读取操作是否成功,如果发生了IO错误,则运算符将给定的对象重置为空Sales_data,这样可以确保对象处于正确的状态。
输入运算符必须处理可能失败的情况,而输出运算符不需要。
输入时的错误
1、当流含有错误类型的数据时读所有取操作可能失败。例如在读完booNo后,输入运算符假定接下来读入的是两个数字数据,一旦输入的不是数字数据,则读取操作及后续对流的其他使用都将失败。
2、当读取操作到达文件末尾或遇到输入流的其他错误时也会失败。
在程序中我们没有逐个检查每个读取操作,而是等读取了所有数据后赶在使用这些数据前一次性检查。当读取操作发生错误时,输入运算符应该从错误中恢复。
对于Sales_data的输入运算符来说,如果给定了下面的输入将发生什么情况?
(a)0-201-99999-9 10 24.95
(a)10 24.95 0-201-99999-9
(a)数据输入正确,(b)数据输入错误,参数中传入的Sales_data对象将会得到默认值。