Урок №211. Состояния потока и валидация пользовательского ввода

  Юрий  | 

  Обновл. 24 Ноя 2020  | 

 12077

 ǀ   6 

На этом уроке мы рассмотрим состояния потока, валидацию пользовательского ввода и какой она бывает, а также нюансы, связанные с этой темой в языке С++.

Состояния потока

Класс ios_base содержит следующие флаги для обозначения состояния потоков:

   goodbit — всё хорошо;

   badbit — произошла какая-то фатальная ошибка (например, программа попыталась прочитать данные после конца файла);

   eofbit — поток достиг конца файла;

   failbit — произошла какая-то НЕ фатальная ошибка (например, пользователь ввел буквы, когда программа ожидала числа).

Хотя эти флаги находятся в ios_base, но, поскольку ios является дочерним классом для ios_base, доступ к этим флагам также возможен и через ios (например, как std::ios::failbit).

ios также предоставляет ряд методов для доступа к вышеперечисленным состояниям потока:

   good() — возвращает true, если установлен goodbit (значит, что с потоком всё ок);

   bad() — возвращает true, если установлен badbit (значит, что произошла какая-то фатальная ошибка);

   eof() — возвращает true, если установлен eofbit (значит, что поток находится в конце файла);

   fail() — возвращает true, если установлен failbit (значит, что произошла какая-то НЕ фатальная ошибка);

   clear() — сбрасывает все текущие флаги состояния потока и задает ему goodbit;

   clear(state) — сбрасывает все текущие флаги состояния потока и устанавливает флаг, переданный в качестве параметра;

   rdstate() — возвращает текущие установленные флаги;

   setstate(state) — устанавливает флаг состояния, переданный в качестве параметра.

Чаще всего мы будем иметь дело с failbit, который срабатывает при некорректном пользовательском вводе. Например:

Обратите внимание, эта программа ожидает от пользователя ввод целого числа. Однако, если пользователь введет что-либо другое (например, Tom), то cin не сможет извлечь что-либо в nAge, и для потока будет установлен флаг failbit.

Если же возникает ошибка, и для потока задан какой-либо другой флаг (отличный от goodbit), то дальнейшие операции с этим потоком будут проигнорированы. Это можно исправить, вызвав метод clear().

Валидация пользовательского ввода


Валидация пользовательского ввода — это процесс проверки того, соответствует ли пользовательский ввод заданным критериям. Обычно, валидация ввода бывает числовой и строковой.

Со строковой валидацией мы принимаем весь пользовательский ввод в качестве строки, а затем либо принимаем эту строку, либо отклоняем её (в зависимости от критериев проверки). Например, если мы просим пользователя ввести номер телефона, то мы должны убедиться, что этот номер состоит из 10 цифр. В большинстве языков (особенно в скриптовых, таких как Perl и PHP) это можно сделать с помощью регулярных выражений. В языке C++ нет встроенной поддержки регулярных выражений (возможно, это добавят в следующих версиях языка C++), поэтому обычно это делается путем проверки каждого символа строки на соответствие заданным критериям.

С числовой валидацией мы обычно заботимся о том, чтобы число, которое ввел пользователь, находилось в определенном диапазоне (например, от 0 до 20). Однако, в отличие от строковой валидации, пользователь может ввести данные, которые вообще не являются числами, а нам нужно будет обрабатывать и такие случаи.

Для решения этой проблемы C++ предоставляет ряд полезных функций, которые мы можем использовать для определения того, являются ли конкретные символы цифрами или буквами. Следующие функции находятся в заголовочном файле cctype:

   функция isalnum(int) — возвращает ненулевое значение, если параметром является буква или цифра;

   функция isalpha(int) — возвращает ненулевое значение, если параметром является буква;

   функция iscntrl(int) — возвращает ненулевое значение, если параметром является управляющий символ;

   функция isdigit(int) — возвращает ненулевое значение, если параметром является цифра;

   функция isgraph(int) — возвращает ненулевое значение, если параметром является выводимый символ (но не пробел);

   функция isprint(int) — возвращает ненулевое значение, если параметром является выводимый символ, включая пробел;

   функция ispunct(int) — возвращает ненулевое значение, если параметром не являются ни буква, ни цифра, ни пробел;

   функция isspace(int) — возвращает ненулевое значение, если параметром является пробел;

   функция isxdigit(int) — возвращает ненулевое значение, если параметром является шестнадцатеричная цифра (0-9, a-f, A-F).

Строковая валидация

Давайте выполним простую строковую валидацию, попросив пользователя ввести свое имя. Наши ограничения: имя может содержать только буквы и пробелы. Если пользователь введет что-либо лишнее, то ввод отклоняется. Перебирать и проверять мы будем каждый символ пользовательского ввода:

Обратите внимание, этот код не идеален: пользователь может ввести в качестве своего имени djskbvjdb jdhsbj js или вообще одни пробелы. Мы можем усилить валидацию, уточнив наши критерии проверки: имя пользователя должно содержать как минимум 1 символ и не более одного пробела.

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

Шаблон будет работать следующим образом:

   # — любая цифра в пользовательском вводе;

   @ — любая буква в пользовательском вводе;

   _ — любой пробел в пользовательском вводе;

   ? — вообще любой символ.

Все символы пользовательского ввода и нашего шаблона должны точно совпадать.

Итак, если мы хотим, чтобы пользовательский ввод соответствовал шаблону (###) ###-####, то пользователь должен ввести: ( три цифры ), пробел, три цифры, тире и еще четыре цифры. Если что-то из этого не совпадет, то пользовательский ввод будет отклонен, например:

Используя эту функцию, мы можем заставить пользователя ввести свой номер телефона точно по заданному нами шаблону. Но это не панацея на все случаи жизни.

Числовая валидация


При работе с числовым вводом очевидным путем развития событий является использование оператора извлечения для конвертации пользовательского ввода в числовой тип. Проверяя failbit, мы можем сказать, ввел ли пользователь число или нет. Например:

Если пользователь ввел число, то cin.fail() будет false, выполнится оператор break, и мы выйдем из цикла while. Если же пользователь ввел букву, то cin.fail() будет true, и пользователю снова будет предложено ввести свой возраст.

Однако, есть один нюанс: если пользователь введет строку, которая начинается с цифр, но затем содержит буквы (например, 53qwerty74), то первые цифры (53) будут извлечены в nAge, а остаток строки (qwerty74) останется во входном потоке, и failbit при этом НЕ будет установлен. Это грозит наличием мусора во входном потоке при следующем извлечении.

Давайте решим эту проблему:

Числовая валидация с помощью строки

Вышеприведенный пример потребовал немало усилий, чтобы получить одно простое значение! Другой способ обработки числового ввода заключается в том, чтобы прочитать пользовательский ввод как строку, обработать его как строку и, если он пройдет проверку, конвертировать эту строку в числовой тип. Например:

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

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


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (62 оценок, среднее: 4,94 из 5)
Загрузка...

Комментариев: 6

  1. Аватар Максим:

    Парочка замечаний к примерам:

    1) Пример с валидацией по шаблону.
    ситуация с кейсами:

    Если вызываем функцию с аргументом "пробел":

    то за валидацию "пробела" отвечает кейс "default".

    Можно поменять аргумент-"шаблон":

    и за валидацию "пробела" будет отвечать case '_'

    Получаются неочевидные на первый взгляд выводы:
    — функции с разными аргументами:

    работают одинаково, и требуют единого ввода от пользователя: "(123) 456-7890"

    — если не вносить изменения в определение функции InputMatches, то невозможно создать аргумент-"шаблон" для функции InputMatches , если мы захотим, чтобы пользователь вводил строку по типу: "(123)_456-7890"

    2) В последнем примере "Числовая валидация с помощью строки"
    после строки 12:

    желательно бы очистить буфер:

    чтобы исключить возможность пользователю оставлять в буфере мусор
    (например: "25 trash" — оставит в буфере "trash", что нехорошо)

    Или можно использовать getline:

    1. Аватар Максим:

      Подумал еще и понял, что некорректно придрался к символу с нижним подчеркиванием. Правильнее было бы сформулировать замечание таким образом:
      Раз символы "#", "?", "@", "_" в функции InputMatches() являются ключами для анализа пользовательского ввода, то эта функция не позволяет, чтобы пользователь вводил непосредственно эти символы.

      Еще одно замечание, которое пропустил.
      Весь курс отмечалось, что по значению нужно передавать только базовые переменные.
      Поэтому и параметрам функции InputMatches() следует принимать ссылки:

  2. Аватар Константин:

    Почему функция isdigit() генерирует исключение при передаче в неё символа кириллицы?

    1. Аватар mikasey:

      Потому что кирилица не входит в стандартную таблицу аски (ASCII)

  3. Аватар koh:

    ух. еще пару уроков и… все?)

    1. Юрий Юрий:

      Не знаю. Пока что нужно довести эти до конца 🙂

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *