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
// Опустошает буфер: // Возвращает EOF при ошибке и 0 в случае успеха virtual int overflow(int c =EOF);
// Заполняет буфер
// Возвращет EOF при ошибке или конце ввода, // иначе следующий char virtual int underflow();
int snextc() // берет следующий char (* return (++qptr==pptr) ? underflow() : *qptr amp;0377; *)
// ...
int allocate() //выделяет некоторое пространство буфера
streambuf() (* /* ... */*) streambuf(char* p, int l) (* /* ... */*) ~streambuf() (* /* ... */*) *);
Обратите внимание, что здесь определяются указатели, нобходимые для работы с буфером, поэтому обычные посимвольные действия можно определить (только один раз) в виде максимално эффективных inlinфункций. Для каждой конкретной стратгии буферизации необходимо определять только функции перепонения overflow() и underflow(). Например:
struct filebuf : public streambuf (*
int fd; // дескриптор файла char opened; // файл открыт
int overflow(int c =EOF); int underflow();
// ...
// Открывает файл: // если не срабатывает, то возвращет 0, // в случае успеха возвращает «this» filebuf* open(char *name, open_mode om); int close() (* /* ... */ *)
filebuf() (* opened = 0; *) filebuf(int nfd) (* /* ... */ *) filebuf(int nfd, char* p, int l) : (p,l) (* /*...*/ *) ~filebuf() (* close(); *) *);
int filebuf::underflow() // заполняет буфер из fd (* if (!opened !! allocate()==EOF) return EOF;
int count = read(fd, base, eptr-base); if (count « 1) return EOF;
qptr = base; pptr = base + count; return *qptr amp; 0377; *)
8.7 Эффективность
Можно было бы ожидать, что раз ввод/вывод «stream.h» определен с помощью общедоступных средств языка, он будет мнее эффективен, чем встроенное средство. На самом деле это не так. Для действий вроде «поместить символ в поток» использются inline-функции, единственные необходимые на этом уровне вызовы функций возникают из-за переполнения сверху и снизу.
Для простых объектов (целое, строка и т.п.) требуется по оному вызову на каждый. Как выясняется, это не отличается от прочих средств ввода/вывода, работающих с объектами на этом уровне.
8.8 Упражнения
1. (*1.5) Считайте файл чисел с плавающей точкой, составьте из пар считанных чисел комплексные числа и выведите комплексные числа.
2. (*1.5) Определите тип name_and_address (имя_и_адрес). Определите для него «„ и “». Скопируйте поток объектов name_and_address.
3. (*2) Постройте несколько функций для запроса и чтения различного вида информации. Простейший пример – функция y_or_n() в #8.4.4. Идеи: целое, число с плавающей токой, имя файла, почтовый адрес, дата, личные данные и т. д. Постарайтесь сделать их защищенными от дурака.
4. (*1.5) Напишите программу, которая печатает (1) все бувы в нижнем регистре, (2) все буквы, (3) все буквы и цифры, (4) все символы, которые могут встречаться в идентификаторах С++ на вашей системе, (5) все символы пунктуации, (6) целые значения всех управляющих симвлов, (7) все символы пропуска, (8) целые значения всех символов пропуска, и (9) все печатаемые символы.