C++ - Страустрап Бьярн
Шрифт:
Интервал:
Закладка:
Состояние потока можно проверять например так:
switch (cin.rdstate()) (* case _good: // последняя операция над cin прошла успешно break; case _eof: // конец файла break; case _fail: // некоего рода ошибка форматирования // возможно, не слишком плохая break; case _bad: // возможно, символы cin потеряны break; *)
Для любой переменной z типа, для которого определены операции «„ и “», копирующий цикл можно написать так:
while (cin»»z) cout «„ z «« «n“;
Например, если z – вектор символов, этот цикл будет брать стандартный ввод и помещать его в стандартный вывод по одному слову (то есть, последовательности символов без пробла) на строку.
Когда в качестве условия используется поток, происходит проверка состояния потока, и эта проверка проходит успешно (то есть, значение условия не ноль) только если состояние _good. В частности, в предыдущем цикле проверялось состояние istream, которое возвращает cin»»z. Чтобы обнаружить, почему цикл или проверка закончились неудачно, можно исследовать состояние. Такая проверка потока реализуется операцией преоразования (#6.3.2).
Делать проверку на наличие ошибок после каждого ввода или вывода действительно не очень удобно, и обычно источником ошибок служит программист, не сделавший этого в том месте, где это существенно. Например, операции вывода обычно не проверяются, но они могут случайно не сработать. Парадигма потка ввода/вывода построена так, чтобы когда в С++ появится (если это произойдет) механизм обработки исключительных ситаций (как средство языка или как стандартная библиотека), его будет легко применить для упрощения и стандартизации обрабоки ошибок в потоках ввода/вывода.
8.4.3 Ввод типов, определяемых пользователем
Ввод для пользовательского типа может определяться точно так же, как вывод, за тем исключением, что для операции ввода важно, чтобы второй параметр был ссылочного типа. Например:
istream amp; operator»»(istream amp; s, complex amp; a) /* форматы ввода для complex; "f" обозначает float: f ( f ) ( f , f ) */ (* double re = 0, im = 0; char c = 0;
s »» c; if (c == '(') (* s »» re »» c; if (c == ',') s »» im »» c; if (c != ')') s.clear(_bad); // установить state *) else (* s.putback(c); s »» re; *)
if (s) a = complex(re,im); return s; *)
Несмотря на то, что не хватает кода обработки ошибок, большую часть видов ошибок это на самом деле обрабатывать бдет. Локальная переменная c инициализируется, чтобы ее значние не оказалось случайно '(' после того, как операция окочится неудачно. Завершающая проверка состояния потока гарантирует, что значение параметра a будет изменяться только в том случае, если все идет хорошо.
Операция установки состояния названа clear() (очистить), потому что она чаще всего используется для установки состония потока заново как _good. _good является значением парметра по умолчанию и для istream::clear(), и для ostream::clear().
Над операциями ввода надо поработать еще. Было бы, в частности, замечательно, если бы можно было задавать ввод в терминах образца (как в языках Snobol и Icon), а потом проврять, прошла ли успешно вся операция ввода. Такие операции должны были бы, конечно, обеспечивать некоторую дополнителную буферизацию, чтобы они могли воссанавливать поток ввода в его исходное состояние после неудачной попытки распознавания.
8.4.4 Инициализация потоков ввода
Естественно, тип istream, так же как и ostream, снабжен конструктором:
class istream (*
// ... istream(streambuf* s, int sk =1, ostream* t =0); istream(int size, char* p, int sk =1); istream(int fd, int sk =1, ostream* t =0); *);
Параметр sk задает, должны пропускаться пропуски или нет. Параметр t (необязательный) задает указатель на ostream, к которому прикреплен istream. Например, cin прикреплен к cout; это значит, что перед тем, как попытаться читать симвлы из своего файла, cin выполняет
cout.flush(); // пишет буфер вывода
С помощью функции istream::tie() можно прикрепить (или открепить, с помощью tie(0)) любой ostream к любому istream. Например:
int y_or_n(ostream amp; to, istream amp; from) /* «to», получает отклик из «from» */ (* ostream* old = from.tie( amp;to); for (;;) (* cout «« "наберите Y или N: "; char ch = 0; if (!cin.get(ch)) return 0;
if (ch != 'n') (* // пропускает остаток строки char ch2 = 0; while (cin.get(ch2) amp; amp; ch2 != 'n') ; *) switch (ch) (* case 'Y': case 'y': case 'n': from.tie(old); // восстанавливает старый tie return 1; case 'N': case 'n': from.tie(old); // восстанавливает старый tie return 0; default: cout «« "извините, попробуйте еще раз: "; *) *) *)
Когда используется буферизованный ввод (как это происхдит по умолчанию), пользователь не может набрав только одну букву ожидать отклика. Система ждет появления символа новой строки. y_or_n() смотрит на первый символ строки, а остальные игноирует.
Символ можно вернуть в поток с помощью функции istream:: putback(char). Это позволяет программе «заглядывать вперед» в поток ввода.
8.5 Работа со строками
Можно осуществлять действия, подобные вводу/выводу, над символьным вектором, прикрепляя к нему istream или ostream. Например, если вектор содержит обычную строку, завершающуюся нулем, для печати слов из этого вектора можно использовать приведенный выше копирующий цикл:
void word_per_line(char v[], int sz) /* печатет "v" размера «sz» по одному слову на строке */ (* istream ist(sz,v); // сделать istream для v char b2[MAX]; // больше наибольшего слова while (ist»»b2) cout «„ b2 «« «n“; *)
Завершающий нулевой символ в этом случае интерпретируеся как символ конца файла.
В помощью ostream можно отформатировать сообщения, котрые не нужно печатать тотчас же:
char* p = new char[message_size]; ostream ost(message_size,p); do_something(arguments,ost); display(p);
Такая операция, как do_something, может писать в поток ost, передавать ost своим подоперациям и т.д. с помощью стадартных операций вывода. Нет необходимости делать проверку на переполнение, поскольку ost знает свою длину и когда он будет переполняться, он будет переходить в состояние _fail. И, нконец, display может писать сообщения в «настоящий» поток ввода. Этот метод может оказаться наиболее полезным, чтобы справляться с ситуациями, в которых окончательное отображение данных включает в себя нечто более сложное, чем работу с трдиционным построчным устройством вывода. Например, текст из ost мог бы помещаться в располагающуюся где-то на экране оласть фиксированного размера.
8.6 Буферизация
При задании операций ввода/вывода мы никак не касались типов файлов, но ведь не все устройства можно рассматривать одинаково с точки зрения стратегии буферизации. Например, для ostream, подключенного к символьной строке, требуется буферзация другого вида, нежели для ostream, подключенного к фалу. С этими пробемами можно справиться, задавая различные бферные типы для разных потоков в момент инициализации (обратите внимание на три конструктора класса ostream). Есть только один набор операций над этими буферными типами, поэтму в функциях ostream нет кода, их различающего. Однако фунции, которые обрабатывают переполнение сверху и снизу, виртальные. Этого достаточно, чтобы справляться с необходимой в данное время стратегией буферизации. Это также служит хорошим примером применения виртуальных функций для того, чтобы сдлать возможной однородную обработку логически эквивалентных средств с различной реализацией. Описание буфера потока в «stream.h» выглядит так:
struct streambuf (* // управление буфером потока
char* base; // начало буфера char* pptr; // следующий свободный char char* qptr; // следующий заполненный char char* eptr; // один из концов буфера char alloc; // буфер, выделенный с помощью new