Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 153
Текст из файла (страница 153)
По умолчанию число считываемых ею символов равно 1, поэтому вызов !апоге () без аргументов означает «выбросить следующий символ». Как и яе1!!пе (), она принимает завершающий символ, если оп встретится, и в этом случае удаляет его из потока ввода. Отметим, что по умолчанию завершающим символом для !даосе () является конец файла.
Для всех этих функций не очевидно, что именно должно завершать чтение — и даже трудно запомнить, какой функции соответствует тот или иной завершающий символ Однако мы всегда проверяем, не достигли ли конца файла Ц 21.3.3). Также введена функция Хсоип1 (), возвращающая число символов, считанных из потока функцией неформатированного ввода в последний раз. Например: оо!дгеад а Иле (оптах) //- 1/ (с!и уа1! () ) ( О плохой формат ввода с!п,с!еаг(), //очистка флагов ввода (у 2!.З.З) стдупоге (тах, ' ); // пропуск до точки с запятой !/ (!с!п) ( // копен потока е!зе !/ (с!п.асоипг () == так) ( // пробзьельа: про ттали максимальное число символов е!зе ( // встретили и выбросили точку с запятой 687 21.3. Ввод //- ) !п1Ь ~ю!и!е ( (! = с!п.яе1 ()) йй !ь=ЕОР) ( ЕОà — это значение ео/() из обычных сЬа!г 1га!1з для сЬаг.
ЕОГ представлен в <!оз1 еат>. Таким образом, этот цикл можно было бы написать как геас( (р, ЫАХ Ю7), но мы специально написали явный цикл, потому что хотели посмотреть на каждый символ при его поступлении. Как уже было сказано, главная сила языка С вЂ” в его способности считывать символы и решать, что с ними ничего ие надо делать — причем выполнять это быстро. Это действительно важное достоинство, которое нельзя недооценивать, и цель С++ —. не утратить его.
Стандартный заголовочный файл <сс(уре> определяет несколько функций, которые могут пригодиться при обработке ввода (Ь' 20.4.2). Например, функция еа1шЬ!1е (), считывающая из входа символы-разделители, может быть определена следующим образом: !зггеатй еигмЬ!1е (Вггеатй !з) ( сЬагс; ыЬ!!е (!з яе1 (с)) ( Яйззразе (с)) ( В ри1басЬ (с), Ьгеад; // с — ив символ -разделитель? // поместить с обротио в буфер вводи ге1игп и Вызов !з.ри(ЬасЬ () превращает с в следующий символ из потока ьз Я 21.бА).
21.3.5. Ввод типов, определяемых пользователем Операции ввода для типов, определяемых пользователем, можно ввести точно так же, как это делалось для операций вывода. Однако для операций ввода важно, чтобы второй аргумент ие был константной ссылкой. Например: К несчастью, если максимальное число символов прочитано, то не существует способа определить, был ли достигнут терминатор (в качестве последнего символа). Функция не1 () без аргументов — это версия из <юз(геат>, аналогичная пе1сЬаг () в <сз1йо> Я 21.8). Она просто считывает символ и возвращает его численное значение.
Таким образом, она освобождает иас от предположений, какой тип символа использовался. Если нет никакого символа, яе1() возвращает соответствующий маркер «конца файлаь (то есть 1! абаз 1уреиеоД() этого потока) и устанавливает поток в состояние ео/'Я 21З.З). Например: воЩ(ипз!дпес(сьаг* р) Глава 21. Потоки 688 мггеат8, орега!ог» (!в!сватам в, сотр1ех8, а) /ч форисчти ввода для сотр1ех (/означает число с плавающей точкой): / !/) (/л / ( поиЫе ге = 0, ип = 0; сйиг с = 0; в»с; (/ (с == ' (') ( в» ге»с; (/ (с == ',1 в» ап» с; (! (с!= ')') в.с!еаг (!ов Ьаве:~а11Ы!1: // установка состояния е!ве ( в.ри(Ьасй (с); в» ге; 1/ (в) а = сотр1ек (ге, ст); ге!ига в; Несмотря на краткость кода обработки ошибок, такая программа в действительности может выловить большинство видов ошибок.
Локальная переменная с инициализируется для того, чтобы ее значение после неудавшейся первой операции» случайно не оказалось ' ('. Заключительная проверка состояния потока гарантирует, что значение аргумента а изменилось, только если все прошло хорошо. Если найдена оп!ибка форматирования, состояние потока устанавливается в!а11Ы1. Состояние пе устанавливается в Ьас(Ы( потому, что поток сам по себе не разрушен. Пользователь может переустановить поток (используя с(ваг (]) и, возможно, обойти проблему и извлечь нз потока полезные данные.
Операция для установки состояния потока называется с1еаг () (очистить), поскольку ее самое распространенное назначение — сброс состояния потока в лоос( (); !ов Ьавеэдоос(Ы! — зто значение аргумента по умолчанию для с1еаг() Я 21.3.3). 21.3.6. Исключения Проверять наличие ошибок после каждой операции ввода/вывода не слишком удобно, поэтому самая распространенная причина ошибок кроется в том, что этого не делают там, где зто важно. В частности, как правило, не проверяются операции вывода, в то время как сбои случаются и при их выполнении. Единственная функция, которая непосредственно изменяет состояние потока,— это с1еаг (). Поэтому очевидный способ заметить изменение состояния — это попросить с1еаг() сгенерировать исключение.
Именно этим и занимается член класса Ьавгс сов функция ехсер!!опв (): !етр1агечс(авв СЬ, с1авв тг = сйаг !га!!в<СЬ» с1авв Ьав!с !ов: риЫ!с !ов Ьаве ( риЫи". //- 689 21.3. Ввод //класс исключений (см, у (ч (О! с!аяя /айиге; //получает состояние исключения // устанавливает состояние исключение гок(аге ехсериопз () сопя!; ио(й ехсергюпз ((оз!асе ехсерф Например, сои!.ехсериот ((ок ЬаяесЬайЬ(! ) (ок ЬаяегеоЯз!с( (оз Ьазесео/6(!) требует, чтобы с1еаг () сгенерировала исключение юз базе::Га((иге, если сои! придет в состояние Ьаг!, /а(! или ео/ — другими словами, если какая-нибудь из операций над сои! нс выполнилась безупречно.
Если необходимо, мы можем проверить сои! для точного определения, что произошло. Подобным же образом си,ехсериопя ((оз Ьаяесбайй!) юя базе;уа((б(!) позволит нам перехватить не слишком экзотический случай, когда ввод производится не в ожидаемом нами формате и операция ввода нс возвращает значения из потока. Вызов ехсер((опя () без аргументов возвращает набор флагов состояния ввода/вывода, которое инициировало исключение. Например; ооЫрг(п! ехсерг!оп ((оя Ьакей юз) ( юк Ьазес(аз!а!в я = юз.ехсериот (); (Г (к8«оз базе-байЫ!) сои! « "генерирует длл бад', (зд!оз базе:/а(!Ь(!) сои! «« "генерирует для/а!!'; Ц' (яд!оя базе сев/бс!) сои! « 'генерирует для ео/'; (/ (з == 0) сои! «" не возбуждает'; Главное назначение исключений ввода/вывода — вылавливать маловероятные, а значит, часто упускаемые ошибки. Вторая их задача — контролировать ввод/вывод.
Например: ооЫ геай!пгз (оес(ог«!и!»б я) //немойлюбимий стиль! ( юк Ьозе !озсаге о!й кса!е = с!и ехсериопя (); //сохриняет состояние исключения ст.ехсериот ((оз баяесео/бгс! // возбуждает для ео/ /ог (л) и"у ( гп! г; с!и»(; я рияд басу (г); ) сассб (!оз баке:/а!(иге) ( Ьгеаб; сш.ехсериопя (оЫ йоте) ); // правильно; дошли до конца файла //восстановливаем состояние исключения Вопрос, который задается при использовании исключений — «Ошибка ли это?» или «Действительно ли это исключительное событие?». Обычно я считаю, что ответ на оба этих вопроса — «нет».
И поэтому я предпочитаю работать напрямую с состоянием потока. То, что можно обработать локальными управляющими структурами внутри функции, редко улучшается за счет использования исключений. Глава 21. Потоки 690 21.3.7. Связывание потоков Функция Пе (( из Ьаз(с 1ов используется для того, чтобы устанавливать и разрывать связи между 1в1 еат и ов1геат: гетр!а1е<с1акк СЬ, с1азк Тг= сдаг 1га11к<СЬ» с1акз кЫ.Ьазсс 1оя: риЬПс гоз Ьаве ( Ьаз1с овггеат<СЬ, Тгь' Ве ((сопев; // получаем указатель нп, связанный поток Ьаз1с оз1геат<СИ, Тг>*1!е(Ьав1с овггеат<СЬ, Тгтз(; //привязываем *гйтвяк //... В Рассмотрим; пой ие1 раввин/ (( ( всг1пд к; сои1« 'Пароль, "; с1п»з; //..
) Как нам гарантировать, что Пароль: появится на экране прежде, чем выполнится операция считывания? Вывод в сои1 буферизуется, так что если с(п и сои1 независимы, то Пароль: не появится на экране, пока не заполнится буфер вывода. Решение этой задачи заключается в том, что сои1 связывается с сьп операцией с(п.11е (с саик(. Если овсгеат связан с 1к1геат, ов1геат очишастся каждый раз, когда операция ввода над гк1геат приводит к переполнению; то есть каждый раз, когда нужен новый символ из конечного источника ввода, чтобы завершить операцию ввода. Таким образом, сои1 « 'Парол к с1п» к; равносильно следующему: сои1 « "Пароль соис/1изЬ ((; скп» К; Поток может иметь самое большое один ок1 еат, связанный с ним в конкретный момент времени. Обращение к в.11е (0( разрывает связь между потоком в и потоком, с которым он был связан (если бгал).
Как и большинство функций с потоками, устанавливающих значение, функция Пе [к( возвращает прежнее значение; то есть она возврашает предыдущий связанный поток или О. При вызове без аргумента Пе (( возвращает текущее значение, не изменяя его. Из стандартных потоков сои1связывается с с(п, а шсои1с шс(п. Потоки сеггце нужно связывать, поскольку они не буферизуются, в то время как потоки с1ов'не предполагают вмешательства пользователя. б91 21.3, Ввод 21.3.8.