【C++基础】第七十六课:[重载运算与类型转换]输入和输出运算符

重载输入输出运算符

Posted by x-jeff on July 7, 2023

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.输入和输出运算符

如我们所知,IO标准库分别使用»和«执行输入和输出操作。对于这两个运算符来说,IO库定义了用其读写内置类型的版本,而类则需要自定义适合其对象的新版本以支持IO操作。

2.重载输出运算符«

通常情况下,输出运算符的第一个形参是一个非常量ostream对象的引用。之所以ostream是非常量是因为向流写入内容会改变其状态;而该形参是引用是因为我们无法直接复制一个ostream对象。

第二个形参一般来说是一个常量的引用,该常量是我们想要打印的类类型。第二个形参是引用的原因是我们希望避免复制实参;而之所以该形参可以是常量是因为(通常情况下)打印对象不会改变对象的内容。

为了与其他输出运算符保持一致,operator«一般要返回它的ostream形参。

2.1.Sales_data的输出运算符

1
2
3
4
5
ostream &operator<<(ostream &os, const Sales_data &item)
{
	os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
	return os;
}

2.2.输出运算符尽量减少格式化操作

通常,输出运算符应该主要负责打印对象的内容而非控制格式,输出运算符不应该打印换行符。

2.3.输入输出运算符必须是非成员函数

与iostream标准库兼容的输入输出运算符必须是普通的非成员函数,而不能是类的成员函数。否则,它们的左侧运算对象将是我们的类的一个对象:

1
2
Sales_data data;
data << cout; //如果operator<<是Sales_data的成员

假设输入输出运算符是某个类的成员,则它们也必须是istream或ostream的成员。然而,这两个类属于标准库,并且我们无法给标准库中的类添加任何成员。

因此,如果我们希望为类自定义IO运算符,则必须将其定义成非成员函数。当然,IO运算符通常需要读写类的非公有数据成员,所以IO运算符一般被声明为友元

3.重载输入运算符»

通常情况下,输入运算符的第一个形参是运算符将要读取的流的引用,第二个形参是将要读入到的(非常量)对象的引用。该运算符通常会返回某个给定流的引用。第二个形参之所以必须是个非常量是因为输入运算符本身的目的就是将数据读入到这个对象中。

3.1.Sales_data的输入运算符

1
2
3
4
5
6
7
8
9
10
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;
}

输入运算符必须处理输入可能失败的情况,而输出运算符不需要。

3.2.输入时的错误

在执行输入运算符时可能发生下列错误:

  • 当流含有错误类型的数据时读取操作可能失败。例如在读取完bookNo后,输入运算符假定接下来读入的是两个数字数据,一旦输入的不是数字数据,则读取操作及后续对流的其他使用都将失败。
  • 当读取操作到达文件末尾或者遇到输入流的其他错误时也会失败。

在上述程序中我们没有逐个检查每个读取操作,而是等读取了所有数据后赶在使用这些数据前一次性检查。

3.3.标示错误

一些输入运算符需要做更多数据验证的工作。例如,我们的输入运算符可能需要检查bookNo是否符合规范的格式。在这样的例子中,即使从技术上来看IO是成功的,输入运算符也应该设置流的条件状态以标示出失败信息。