Операторы ввода и вывода, соответствующие соглашениям библиотеки iostream, должны быть обычными функциям, а не членами класса. Эти операторы не могут быть членами нашего класса. Если бы это было так, то левый операнд должен был быть объектом типа нашего класса:
Sales_data data;
data << cout; //
//
Если бы эти операторы были членами некоего класса, то они должны были бы быть членами класса istream или ostream. Но эти классы являются частью стандартной библиотеки, а добавлять члены в библиотечные классы нельзя.
Таким образом, если необходимо определить операторы ввода-вывода для собственного типа, их следует определить как функции, не являющиеся членами класса. Конечно, операторы ввода-вывода обычно должны читать или выводить данные не открытых переменных-членов. Как следствие, операторы ввода-вывода обычно объявляют дружественными (см. раздел 7.2.1).
Упражнение 14.6. Определите оператор вывода для класса Sales_data.
Упражнение 14.7. Определите оператор вывода для класса String, написанного для упражнений раздела 13.5.
Упражнение 14.8. Определите оператор вывода для класса, который был выбран в упражнении 7.40 раздела 7.5.1.
>>
Обычно первый параметр оператора ввода является ссылкой на поток, из которого осуществляется чтение, а второй параметр — ссылкой на некий неконстантный объект, в который предстоит прочитать данные. Обычно оператор возвращает ссылку на свой поток. Второй параметр не должен быть константным потому, что задачей оператора ввода и является собственно запись данных в этот объект.
Sales_dataВ качестве примера напишем оператор ввода для класса Sales_data:
istream &operator>>(istream &is, Sales_data &item) {
double price; //
//
is >> item.bookNo >> item.units_sold >> price;
if (is) //
item.revenue = item.units_sold * price;
else
item = Sales_data(); //
//
return is;
}
За исключением оператора if это определение подобно прежней функции read() (см. раздел 7.1.3). Оператор if проверяет, было ли чтение успешно. Если произойдет ошибка ввода-вывода, он вернет объект Sales_data в состояние пустого объекта. Это гарантирует корректность состояния объекта.
В операторе ввода возможны следующие ошибки.
• Операция чтения может потерпеть неудачу из-за наличия в потоке данных неподходящего типа. Например, после чтения переменной-члена bookNo оператор ввода подразумевает, что следующие два элемента будут числовыми данными. Если во вводе окажутся не числовые данные, поток будет недопустим и все последующее попытки чтения из него потерпят неудачу.
• Во время любой из операций чтения может встретиться конец файла или произойти другая ошибка потока ввода.
Чтобы не проверять каждую часть прочитанных данных, можно проверить состояние потока в целом и только потом использовать прочитанные данные
if (is) //
item.revenue = item.units_sold * price;
else
item = Sales_data(); //
//
При сбое любой из операций чтения значение переменной-члена price останется неопределенным. Следовательно, перед ее использованием следует проверить, допустим ли еще поток ввода. Если это так, осуществляется вычисление значения переменной revenue. В случае ошибки ничего страшного не произойдет, поскольку будет возвращен пустой объект класса Sales_data. Для этого объекту item присваивается новый объект класса Sales_data, созданный при помощи стандартного конструктора. После этого присвоения переменная-член bookNo объекта item будет содержать пустую строку, а его переменные члены revenue и units_sold — нулевое значение.