что она чаще всего используется для установки сосояния потока
заново как _good. _good является значением параметра по умолчанию и
для istream::clear(), и для ostream::clear().
Над операциями ввода надо поработать еще. Было бы, в частности,
замечательно, если бы можно было задавать ввод в терминах шаблона
(как в языках Снобол и Икон), а потом проверять, прошла ли успешна
вся операция ввода. Такие операции должны были бы, конечно,
обеспечивать некоторую дополнительную буферизацию, чтобы они могли
воссанавливать поток ввода в его исходное состояние после неудачной
попытки распознавания.
8.4.4 Инициализация Потоков Ввода
Естественно, тип istream, так же как и ostream, снабжен
конструктором:
- стр 248 -
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& to, istream& from)
/*
"to", получает отклик из "from"
*/
{
ostream* old = from.tie(&to);
for (;;) {
cout << "наберите Y или N: ";
char ch = 0;
if (!cin.get(ch)) return 0;
if (ch != '\n') { // пропускает остаток строки
char ch2 = 0;
while (cin.get(ch2) && 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). Это позволяет программе "заглядывать
вперед" в поток ввода.
- стр 249 -
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 нет кода, их
различающего. Однако функции, которые обрабатывают переполнение
сверху и снизу, виртуальные. Этого достаточно, чтобы справляться с
необходимой в данное время стратегией буферизации. Это также служит
хорошим примером применения виртуальных функций для того, чтобы
- стр 250 -
сделать возможной однородную обработку логически эквивалентных
средств с различной реализацией. Описание буфера потока в
выглядит так:
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&0377;
}
// ...
int allocate() // выделяет некоторое пространство буфера
streambuf() { /* ... */}
streambuf(char* p, int l) { /* ... */}
~streambuf() { /* ... */}
};
Обратите внимание, что здесь определяются указатели, необходимые
для работы с буфером, поэтому обычные посимвольные действия можно
определить (только один раз) в виде максимально эффективных inline-
функций. Для каждой конкретной стратегии буферизации необходимо
определять только функции переполнения overflow() и underflow().
Например:
- стр 251 -
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 & 0377;
}
8.7 Эффективность
Можно было бы ожидать, что раз ввод/вывод определен с
помощью обцедоступных средств языка, он будет менее эффективен, чем
встроенное средство. На самом деле это не так. Для действий вроде
"поместить символ в поток" используются inline-функции,
единственные необходимые на этом уровне вызовы функций возникают
из-за переполнения сверху и снизу. Для простых объектов (целое,
строка и т.п.) требуется по одному вызову на каждый. Как
выясняется, это не отличается от прочих средств ввода/вывода,
работающих с объектами на этом уровне.
8.8 Упражнения
1. (*1.5) Считайте файл чисел с плавающей точкой, составьте из
пар считанных чисел комплексные числа и выведите комплексные
числа.
- стр 252 -
2. (*1.5) Определите тип name_and_address (имя_и_адрес).
Определите для него << и >>. Скопируйте поток объектов
name_and_address.
3. (*2) Постройте несколько функций для запроса и чтения
различного вида информации. Простейший пример - функция
y_or_n() в #8.4.4. Идеи: целое, число с плавающей точкой, имя
файла, почтовый адрес, дата, личные данные и т.д. Постарайтесь
сделать их защищенными от дурака.
4. (*1.5) Напишите программу, которая печатает (1) все буквы в
нижнем регистре, (2) все буквы, (3) все буквы и цифры, (4) все
символы, которые могут встречаться в идентификаторах C++ на
вашей системе, (5) все символы пунктуации, (6) целые значения
всех управляющих символов, (7) все символы пропуска, (8) целые
значения всех символов пропуска, и (9) все печатаемые символы.
5. (*4) Реализуйте стандартную библиотеку ввода/вывода C
() с помощью стандартной библиотеки ввода/вывода C++
().
6. (*4) Реализуйте стандартную библиотеку ввода/вывода C++
() с помощью стандартной библиотеки ввода/вывода C
().
7. (*4) Реализуйте стандартные библиотеки C и C++ так, чтобы они
могли использоваться одновременно.
8. (*2) Реализуйте класс, для которого [] перегружено для
реализации случайного чтения символов из файла.
9. (*3) Как Упражнение 8, только сделайте, чтобы [] работало и
для чтения, и для записи. Подсказка: сделайте, чтобы []
возвращало объект "дескрипторного типа", для которого
присваивание означало бы присвоить файлу через дескриптор, а
неявное преобразование в char означало бы чтение из файла
через дескриптор.
10. (*2) Как Упражнение 9, только разрешите [] индексировать
записи некоторого вида, а не символы.
11. (*3) Сделайте обобщенный вариант класса, определенного в
Упражнении 10.
12. (*3.5) Разработайте и реализуйте операцию ввода по
сопосталению с образцом. Для спецификации образца используйте
строки формата в духе printf. Должна быть возможность
попробовать сопоставить со вводом несколько образцов для
нахождения фактического формата. Можно было бы вывести класс
ввода по образцу из istream.
13. (*4) Придумайте (и реализуйте) вид образцов, которые намного
лучше.
------------------------------------------------------------------------
Last-modified: Thu, 25-Dec-97 04:53:57 GMT