Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 43 из 43)

что она чаще всего используется для установки сосояния потока

заново как _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 != '&bsol;n') { // пропускает остаток строки

char ch2 = 0;

while (cin.get(ch2) && ch2 != '&bsol;n') ;

}

switch (ch) {

case 'Y':

case 'y':

case '&bsol;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 << "&bsol;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