УДК 004.42

ИСПОЛЬЗОВАНИЕ МЕТОДОВ КЛАССА IOS_BASE ДЛЯ ПРОВЕРКИ СОСТОЯНИЯ ПОТОКА ВВОДА В С++

Дмитриев Владислав Леонидович1, Зайцева Нина Леонидовна1
1Стерлитамакский филиал Башкирского государственного университета

Аннотация
В работе рассматриваются вопросы, связанные с наиболее распространенной ошибкой при вводе данных – ввод данных, не соответствующих указанному в программе формату. Для обработки таких ситуаций можно использовать методы класса ios_base. Они позволяют сообщать о состоянии потока или изменять его. Обсуждаемые возможности будут полезны начинающим программистам и учащимся, изучающим программирование.

Ключевые слова: класс ios_base, потоки ввода-вывода, язык программирования С++


THE USE OF METHODS OF CLASS IOS_BASE FOR CHECK THE STATUS OF THE INPUT STREAM IN C++

Dmitriev Vladislav Leonidovich1, Zaytseva Nina Leonidovna1
1Sterlitamak branch of the Bashkir state University

Abstract
The paper discusses the issues related to the most common mistake at entering data – the enter data that is not specified in the program format. To handle such situations you can use the methods of class ios_base. They allow you to report a state of flux or change it. The discussed capabilities will be useful to novice programmers and students learning programming.

Keywords: input/output streams, the C++programming language, the ios_base class


Рубрика: 05.00.00 ТЕХНИЧЕСКИЕ НАУКИ

Библиографическая ссылка на статью:
Дмитриев В.Л., Зайцева Н.Л. Использование методов класса ios_base для проверки состояния потока ввода в С++ // Современные научные исследования и инновации. 2016. № 12 [Электронный ресурс]. URL: http://web.snauka.ru/issues/2016/12/75931 (дата обращения: 02.10.2017).

При обучении программированию на С++ необходимо уделять достаточно внимания работе с потоками ввода-вывода [1], так как практически любая программа использует ввод и вывод. Однако почти всегда изучение этой темы ограничивается лишь стандартным использованием операций ввода и вывода на основе cin и cout. Другие особенности и возможности потоков остаются так и не затронутыми, и неизвестными для учащихся. С целью устранения такого ограничения в статье рассматриваются отдельные вопросы, связанные с проверкой состояния потоков ввода-вывода. Предполагается, что учащиеся уже имеют базовые представления о языке программирования C++.

Класс istream определяет оператор >> (“прочесть из”) для организации ввода встроенных типов. По умолчанию операция >> используется также в качестве битовой операции сдвига, однако это не имеет отношения к вводу: класс istream переопределяет операцию >> для организации ввода, причем эта операция перегружена и может применяться со всеми базовыми типами C++. Класс istream вводит оператор >> для каждого из этих типов данных, причем оператор >> возвращает ссылку на объект istream.

При вводе данных операция >> читает всё, начиная с первого символа (не являющегося символом-разделителем) до следующего символа, который не соответствует типу объекта назначения. Поэтому наиболее распространенной ошибкой при использовании потоков istream является ошибка, связанная с вводом данных, не соответствующих указанному формату. В таких случаях нужно или проверять состояние потока ввода перед использованием считанных данных, или использовать механизм исключений [4-6].

Рассмотрим, например, следующий фрагмент кода:

float a, b;

cin>>a>>b;

cout<<a<<” “<<b;

Если при вводе данных указать, например

1.234w

то значение переменной a будет равно 1.234, но значение переменной b будет неопределенно, т.к. сразу после ввода 1.234w программа перейдет к строке с инструкцией cout. Дело в том, что как только в потоке встретится символ w, являющийся некорректным с точки зрения ожидаемого формата вводимых данных, последним принятым символом будет 4. Символ w останется во входном потоке, и следующий оператор >> начнет чтение с этой точки, что и приводит к неопределенности. Поэтому следующий фрагмент кода

float a; char b;

cin>>a>>b;

cout<<a<<” “<<b;

будет работать верно при вводе строки 1.234w.

Может случиться так, что самое первое введенное значение будет ошибочным с точки зрения формата. В этом случае операция >> оставит значение переменной без изменений и вернет значение 0 (false), что позволит проверить, отвечал ли ввод требованиям установленного формата данных для переменной. Ниже представлен фрагмент программы, в котором вводятся числа, и ведется подсчет их суммы до тех пор, пока вводятся корректные значения для переменной.

float a, s=0.;

while(cin>>a) s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s<<endl;

Пусть в процессе ввода часть чисел вводятся через нажатие клавиши “Enter“, причем это будут корректные данные; затем, в следующей строке, вводится последовательность чисел через символ-разделитель (пробел):

1.37

2.45

1.23 4 13.4 2.2b 3.4 5.6

Результатом работы фрагмента программы будет:

Значение переменной a: 2.2

Сумма чисел: 24.65

Ввод cin буферизуется, поэтому третья строка значений, введенных с клавиатуры, не будет пересылаться программе до тех пор, пока не будет нажата клавиша “Enter” для каждой из выше расположенных строк. Цикл прекратит работу, как только встретится символ “b” (третья строка), так как он не соответствует формату вещественного числа: несоответствие символа “b” ожидаемому формату приводит к тому, что выражение cin>>a будет оценено как false.

Компилятор Borland C++ 5.02 и Microsoft Visual C++ 2005 Express, как и ожидалось, дали в примере выше для переменной a один и тот же результат: 2.2. Однако компилятор wxDev-C++ 7.4 сбрасывает значение переменной a в ноль. Последнее противоречит принципу, принятому в С++: если попытаться прочитать в некоторую переменную и операция не выполняется, значение переменной должно остаться неизменным [1].

Объекты cin и cout содержат член данных, который описывает состояние потока. Состояние потока определяется тремя элементами класса ios_base: eofbit, badbit, failbit. Значение каждого такого элемента может принимать значения 0 (сброшен) или 1 (установлен). Так, когда cin достигает конца файла, eofbit устанавливается в значение 1. Если cin не может прочитать ожидаемый формат данных (как в примерах выше) или производится попытка чтения недоступного файла, то failbit устанавливается в значение 1. Элемент badbit устанавливается в 1, если происходит не поддающийся диагностике сбой, который может привести к повреждению потока. Стоит отметить, что различие между состояниями failbit и badbit очень незначительно. Можно сказать, что если поток в состоянии failbit, но не в badbit, то он не испорчен (при этом символы могут быть потеряны); однако если имеет место состояние badbit, то ни о чем с уверенностью сказать нельзя. Считается, что с потоком все в порядке, если все три первых бита состояния установлены в 0. Таким образом, состояние потока представляется в виде набора флагов (кроме перечисленных выше трех флагов есть еще флаг goodbit, но он выступает просто как другой способ выражения состояния 0).

Класс ios_base предоставляет также некоторые методы, которые позволяют сообщать о состоянии потока, или изменять его. Эти методы представлены в таблице 1.

Таблица 1. Методы управления состояниями потоков

Метод класса ios_base Описание
eof() Возвращает true, если установлен флаг eofbit
bad() Возвращает true, если установлен флаг badbit
fail() Возвращает true, если установлен один из флагов: badbit или failbit
good() Возвращает true, если поток можно использовать (все биты в состоянии 0)
rdstate() Возвращает состояние потока
clear(iostate x) Устанавливает состояние потока в x (по умолчанию x есть 0).
Создает исключение basic_ios::failure в случае (rdstate() & exceptions()) != 0
setstate(iostate x) Вызывает clear(rdstate() | x), в результате чего биты состояния устанавливаются в соответствии с тем, что содержит x. Остальные биты состояния потока остаются неизменными.
exceptions() Возвращает битовую маску для идентификации флагов, ставших причиной исключения.
exceptions(iostate x) Устанавливает состояния, которые вызовут clear() для генерации исключения. Так, если x – это failbit, то clear() возбудит исключение, когда будет установлен failbit.

Если для потока установилось состояние good(), то предыдущая операция выполнилась успешно, и можно выполнять следующий ввод; в противном случае ввод не выполнится.

Метод clear() устанавливает состояние в соответствии со значением переданного аргумента. Например, вызов

cin.clear();

использует аргумент по умолчанию (0), поэтому очищаются все три бита состояния (устанавливаются в 0). Однако вызов

cin.clear(eofbit);

устанавливает флаг eofbit в 1, а остальные два бита оставшихся флагов очищаются.

Метод setstate(), в отличие от clear(), касается только тех бит, которые установлены для его аргумента. Поэтому, например, вызов

cin.setstate(eofbit);

устанавливает только флаг eofbit в значение 1, а остальные биты не изменяются.

Наиболее частой причиной использования сброса состояния потока является необходимость осуществления повторного ввода после получения данных не того формата, что ожидалось. Установка битов состояния потока очень важна: если введенные данные не соответствуют ожидаемому формату, то поток закрывается для дальнейшего ввода или вывода до тех пор, пока соответствующий бит не будет очищен. Например, следующий код не будет работать (повторно ввести значение для a не удастся):

float a, s=0.;

void *p;

while((p=cin>>a) && a !=0) s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s<<endl;

if (p==0) {cout<<”Обнаружена ошибка ввода. ”

<<”Повторите ввод: “;

cin>>a;

s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s; }

Чтобы фрагмент программы продолжил читать ввод после возникновения ошибки, необходимо сбросить биты состояния потока в нормальное состояние. Это можно сделать, используя метод clear(). Однако только одного сброса состояния потока недостаточно, т.к. некорректный ввод все также остается во входной очереди, и будет вновь прочитан. Здесь можно предложить по крайней мере три способа решения проблемы.

Во-первых, можно продолжить чтение до очередного символа-разделителя (по умолчанию – “пробел”), для чего использовать функцию isspace():

float a, s=0.;

void *p;

while((p=cin>>a) && a!=0) s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s<<endl;

if (p==0) {cout<<”Обнаружена ошибка ввода. ”

<<”Повторите ввод: “;

cin.clear(); // сброс состояния потока

while(!isspace(cin.get())); // продолжаем //считывать до символа-разделителя

cin>>a; s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s; }

Введем в качестве данных, например, следующие:

2.45

1.23 4 13.4 2.2b 3.4 5.6

При этом в качестве следующего значения переменной a автоматически (без нового ввода с клавиатуры) будет взято следующее корректное значение из буфера ввода, которое равно 3.4. Поэтому на экране увидим:

Значение переменной а: 2.2

Сумма чисел: 23.28

Обнаружена ошибка ввода. Повторите ввод:

Значение переменной а: 3.4

Сумма чисел: 26.68

Во-вторых, можно отбросить остаток строки буфера, а не очередные символы из него:

float a, s=0.;

void *p;

while((p=cin>>a) && a!=0) s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s<<endl;

if (p==0) {cout<<”Обнаружена ошибка ввода. ”

<<”Повторите ввод: “;

cin.clear(); // сброс состояния потока

while(cin.get() != ‘n’); //отбрасываем остаток строки

cin>>a; s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s; }

Возможно отбросить остаток строки буфера еще двумя способами:

  • Воспользоваться функцией-членом ignore() класса istream, которая извлекает из потока символы и отбрасывает их. Функция имеет два аргумента: количество n отбрасываемых символов и символ-признак, после которого отбрасывание прекращается. Таким образом, функция ignore() отбрасывает либо n символов, либо все символы до первой встречи символа-признака. Так как нам нужно отбросить весь остаток строки в буфере, то в коде выше вместо строки

while(cin.get() != ‘n’);

необходимо написать строку:

cin.ignore(numeric_limits<streamsize>::max(), ‘n’);

При этом также необходимо подключить файл limits. Способ не применим к компилятору Borland C++ 5.02.

  • Воспользоваться функцией-членом rdbuf() класса istream для обращения к буферу ввода. Чтобы узнать, сколько символов в нем содержится, нужно вызвать функцию in_avail() этого буфера – это количество символов мы будем игнорировать. Поэтому в коде выше вместо строки

while(cin.get() != ‘n’);

необходимо написать строку:

cin.ignore(cin.rdbuf()->in_avail());

Данный способ не стал работать с компилятором wxDev-C++ 7.4; компиляторы Borland C++ 5.02 и Microsoft Visual C++ 2005 Express дали при этом одинаковый верный результат.

В-третьих, можно воспользоваться методом sync(), который позволяет очистить буфер стандартного ввода от имеющихся в нем символов:

float a, s=0.; void *p;

while((p=cin>>a) && a!=0) s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s<<endl;

if (p==0) {cout<<”Обнаружена ошибка ввода. ”

<<”Повторите ввод: “;

cin.clear(); // сброс состояния потока

cin.sync();

cin>>a; s+=a;

cout<<”nЗначение переменной а: “<<a;

cout<<”nСумма чисел: “<<s; }

Стоит отметить, что синхронизация потока istream и буфера, выполняемая на основе sync(), не всегда выполняется правильно. Кроме того, функция sync() для буфера, связанного с ostream, сбрасывает содержимое буфера на устройство вывода.

Рассмотрим еще небольшой фрагмент кода, позволяющий подсчитывать сумму вещественных чисел последовательности, признаком конца которой является 0. При этом код обрабатывает ситуации с некорректно введенными данными. В качестве тренировки предлагаем читателю изменить ниже расположенный код таким образом, чтобы из подсчета полностью исключалось некорректно введенное число.

float a, s=0.;

void *p;

do { p=cin>>a;

if (p==0) { cin.clear(); cin.sync(); }

else s+=a; } while(a!=0);

cout<<”nСумма чисел: “<<s;

После изучения представленного выше материала с целью оценки уровня усвоения учащимися учебного материала может оказаться удобным использование всевозможных программ-тестов [2, 3], в том числе в дистанционной форме.


Библиографический список
  1. Дмитриев В.Л. Теория и практика программирования на С++. – Стерлитамак: РИО СФ БашГУ, 2013. – 308 с.
  2. Дмитриев В.Л. Тестирование в игровой форме как способ проверки усвоения учебного материала // Информатика в школе. 2012. №10 (83). – С. 41-43.
  3. Дмитриев В.Л. Компьютерная программа для проведения тестирования с поддержкой произвольного расположения материалов теста // Информатика и образование. 2014. №2 (251). – С. 74-77.
  4. Прата С. Язык программирования С++. Лекции и упражнения, 5-е изд.: Пер. с англ. – М.: Вильямс, 2007. – 1184 с.
  5. Страуструп Б. Язык программирования С++. Специальное издание. –  М.: Бином, 2004. – 1054 с.
  6. Stroustrup Bjarne. The C++ programming language / Bjarne Stroustrup. – Fourth edition. – Boston: Addison-Wesley, 2013. – 1368 p.


Все статьи автора «Дмитриев Владислав Леонидович»


© Если вы обнаружили нарушение авторских или смежных прав, пожалуйста, незамедлительно сообщите нам об этом по электронной почте или через форму обратной связи.

Связь с автором (комментарии/рецензии к статье)

Оставить комментарий

Вы должны авторизоваться, чтобы оставить комментарий.

Если Вы еще не зарегистрированы на сайте, то Вам необходимо зарегистрироваться: