О том, как можно проверять значения, введёные пользователем
В любых программных продуктах, будь то windows-приложение или web-сайт, получение информации от пользователей зачастую осуществляется с помощью форм ввода данных.
Конечно же, нельзя быть абсолютно уверенным, что пользователь введёт именно то, что нужно, поэтому все данные необходимо тщательно проверить.
Как правило, алгоритм проверки этих данных один и тот же: «Если значение поля удовлетворяет требованию, то проверить следующее требование, иначе вывести сообщение об ошибке. Перейти к проверке значения следующего поля».
На практике это выливается с довольно длинные последовательности «if-else». Лично мне это жутко не нравилось, так как сложно с первого взгляда определить, какие поля как проверяются и какие сообщения выдаются в случае ошибок. А ведь полей в форме может быть и десять, тогда код проверки вообще затягивается. Вобщем, я задумался над тем, как можно минимизировать объём работ и вот что из этого получилось.
Я представил проверку как преобразование значения одного типа в значение другого типа (например, проверка того, что в поле введено чило, это преобразование строки в число). Тоесть проверка — это некая функция, имеющая сигнатуру делегата System.Converter .
Для проверки значение помещаем в класс обёртку:
Суть проверки заключается в последовательном вызове методов-расширений для объектов класса ValidationStep , которые опять же возращают объект класса ValidationStep . Это позволяет создавать цепочки проверок. Вот пример таких методов-расширений:
public static class ValidationExtensions
<
public static ValidationStep Validate ( this T value )
<
return new ValidationStep < Value = value >;
>
public static ValidationStep Validate ( this ValidationStep step,
Predicate predicate, string message)
<
if (predicate(step.Value)) return step;
throw new ValidationException(message);
>
public static ValidationStep Convert ( this ValidationStep step,
Converter converter, string message)
<
try < return converter(step.Value).Validate(); >
catch < throw new ValidationException(message); >
>
>
Первый метод используется, чтобы можно было проверять объекты любых типов, торой метод осуществляет проверку на соответствие предикату, а третий на возможность преобразования значения из одного типа в другой. Также можно написать любые другие проверки, например на соответствие целого числа диапазону. Главное, что в случае неудачной проверки должно возникать исключение ValidationException, которое содержит сообщение с текстом ошибки.
Для осуществления самой проверки можно использовать следующий класс, который будет заниматься перехватом исключений:
public static class Validation
<
public static bool Validate (T value , Converter validator,
Action onSuccess, Action onFailure)
<
try
<
var result = validator( value );
onSuccess(result);
return true ;
>
catch (ValidationException e)
<
onFailure(e);
return false ;
>
>
>
А теперь о том, как это использовать. Допустим нам нужно проверить текстовое поле (tb1) и убедиться, что в него введено целое число в диапазоне от 0 до 10. Это можно сделать так:
Validation.Validate(
tb1.Text, value => value.Validate()
.Validate(x => x.Length > 0, «Введите что-нибудь» )
. Convert (x => Convert .ToInt32(x), «Введёное значение не является целым числом» )
.Validate(x => x >= 0, «Число не должно быть меньше нуля» )
.Validate(x => x «Число не должно быть больше десяти» ),
v => MessageBox.Show( «Введено корректное значение: » + v.Value),
e => MessageBox.Show(e.Message));
Учитывая, что проверок может быть больше, да и число полей в форме, как правило, больше одного, такой способ проверок может быть очень даже удобен.
Источник
23.5 – Состояния потока и проверка корректности входных данных
Состояния потока
Класс ios_base содержит несколько флагов состояния, которые используются для сигнализации различных условий, которые могут возникнуть при использовании потоков:
Флаг | Назначение |
---|---|
goodbit | Всё в порядке |
badbit | Произошла какая-то фатальная ошибка (например, программа попыталась прочитать после конца файла) |
eofbit | Поток достиг конца файла |
failbit | Произошла нефатальная ошибка (например, пользователь ввел буквы, когда программа ожидала целое число) |
Хотя эти флаги находятся в ios_base , но поскольку ios является производным от ios_base , а ios требует меньше ввода текста, чем ios_base , доступ к ним обычно осуществляется через него (например, как std::ios::failbit ).
ios также предоставляет ряд функций-членов для удобного доступа к этим состояниям:
Функция-член | Назначение |
---|---|
good() | Возвращает true , если установлен goodbit (поток в норме) |
bad() | Возвращает true , если установлен badbit (произошла фатальная ошибка) |
eof() | Возвращает true , если установлен eofbit (поток находится в конце файла) |
fail() | Возвращает true , если установлен failbit (произошла нефатальная ошибка) |
clear() | Очищает все флаги и восстанавливает поток в состояние goodbit |
clear(state) | Очищает все флаги и устанавливает флаг состояния, переданный в параметре |
rdstate() | Возвращает текущие установленные флаги |
setstate(state) | Устанавливает флаг состояния, переданный в параметре |
Чаще всего мы имеем дело failbit , который устанавливается, когда пользователь вводит недопустимые входные данные. Например, рассмотрим следующую программу:
Обратите внимание, что эта программа ожидает, что пользователь введет целое число. Однако если пользователь вводит нечисловые данные, такие как » Alex «, cin не сможет извлечь что-либо для переменной возраста age , и будет установлен бит отказа failbit .
Если такая ошибка возникает, и для потока устанавливается значение, отличное от goodbit , дальнейшие операции с этим потоком будут проигнорированы. Это условие можно устранить, вызвав функцию clear() .
Проверка корректности входных данных
Валидация (проверка корректности) входных данных – это процесс проверки того, соответствует ли пользовательский ввод некоторому набору критериев. Валидацию ввода обычно можно разделить на два типа: строковую и числовую.
При проверке строки мы принимаем весь пользовательский ввод как строку, а затем принимаем или отклоняем эту строку в зависимости от того, правильно ли она отформатирована. Например, если мы просим пользователя ввести номер телефона, мы можем проверить, что вводимые данные содержат десять цифр. В большинстве языков (особенно в скриптовых языках, таких как Perl и PHP) это делается с помощью регулярных выражений. Стандартная библиотека C++ также имеет библиотеку регулярных выражений. Регулярные выражения медленны по сравнению с проверкой строк вручную, и их следует использовать только в том случае, если производительность (время компиляции и время выполнения) не вызывает беспокойства, или ручная проверка слишком обременительна.
При числовой проверке мы обычно заботимся о том, чтобы число, вводимое пользователем, находилось в определенном диапазоне (например, от 0 до 20). Однако, в отличие от проверки строк, пользователь может вводить вещи, которые вообще не являются цифрами, и нам также необходимо обрабатывать эти случаи.
Чтобы помочь нам, C++ предоставляет ряд полезных функций, которые мы можем использовать для определения того, являются ли конкретные символы цифрами или буквами. В заголовке cctype находятся следующие функции:
Функция | Назначение |
---|---|
std::isalnum(int) | Возвращает ненулевое значение, если параметр представляет собой букву или цифру. |
std::isalpha(int) | Возвращает ненулевое значение, если параметр представляет собой букву. |
std::iscntrl(int) | Возвращает ненулевое значение, если параметр является управляющим символом. |
std::isdigit(int) | Возвращает ненулевое значение, если параметр является цифрой. |
std::isgraph(int) | Возвращает ненулевое значение, если параметр является печатным символом, который не является пробелом. |
std::isprint(int) | Возвращает ненулевое значение, если параметр является печатным символом (включая пробелы). |
std::ispunct(int) | Возвращает ненулевое значение, если параметр не является ни буквенно-цифровым, ни пробельным символом. |
std::isspace(int) | Возвращает ненулевое значение, если параметр – пробельный символ. |
std::isxdigit(int) | Возвращает ненулевое значение, если параметр является шестнадцатеричной цифрой (0-9, a-f, A-F). |
Проверка строки
Примечание автора
С этого момента мы будем использовать функции, которые (пока) не описаны в этой серии статей. Если вы хорошо разбираетесь в C++, возможно, вы сможете понять, что делают эти функции, исходя из их названий и того, как они используются. Советуем посмотреть эти новые функции и типы в справочнике, чтобы лучше понять, что они делают, и для чего еще их можно использовать.
Давайте рассмотрим простой случай проверки строки, попросив пользователя ввести свое имя. Нашим критерием проверки будет то, что пользователь вводит только буквенные символы или пробелы. Если встретится что-то еще, ввод будет отклонен.
Когда дело доходит до входных данных переменной длины, лучший способ проверить строку (помимо использования библиотеки регулярных выражений) – это пройти по всем символам строки и убедиться, что они соответствует критериям проверки. Это именно то, что мы собираемся здесь сделать, или, лучше сказать, то, что std::all_of сделает для нас.
Обратите внимание, что этот код не идеален: пользователь мог сказать, что его имя » asf w jweo s di we ao «, или какая-то другая тарабарщина, или, что еще хуже, просто несколько пробелов. Мы могли бы решить эту проблему, уточнив наши критерии проверки, чтобы принимать только строки, содержащие хотя бы один символ и не более одного пробела.
Теперь давайте рассмотрим другой пример, в котором мы попросим пользователя ввести свой номер телефона. В отличие от имени пользователя, которое имеет переменную длину и критерии проверки одинаковы для каждого символа, номер телефона имеет фиксированную длину, но критерии проверки различаются в зависимости от положения символа. Следовательно, для валидации нам необходим другой подход. В этом случае мы собираемся написать функцию, которая будет проверять данные, введенные пользователем, на соответствие заранее определенному шаблону, чтобы увидеть, соответствует ли они ему. Шаблон будет работать следующим образом:
- # будет соответствовать любой цифре в данных, введенных пользователем;
- @ будет соответствовать любому буквенному символу в пользовательском вводе;
- _ будет соответствовать любому пробельному символу;
- ? будет соответствовать чему угодно;
- в противном случае символы в данных, введенных пользователем, и в шаблоне должны точно совпадать.
Итак, если мы спрашиваем функцию, соответствует ли строка шаблону » (###) ###-#### «, это означает, что мы ожидаем, что пользователь введет символ ‘ ( ‘, три цифры, символ ‘ ) ‘, пробел, три числа, дефис и еще четыре числа. Если что-либо из этого не совпадает, ввод будет отклонен.
Используя эту функцию, мы можем заставить пользователя вводить данные, точно соответствующие нашему конкретному формату. Однако эта функция всё еще имеет несколько ограничений: Если # , @ , _ и ? являются допустимыми символами в пользовательском вводе, эта функция не будет работать, потому что этим символам присвоено особое значение. Кроме того, в отличие от регулярных выражений, здесь нет шаблонного символа, означающего, что «можно ввести переменное количество символов». Таким образом, такой шаблон нельзя использовать для обеспечения того, чтобы пользователь вводил два слова, разделенных пробелом, поскольку он не может обработать тот факт, что слова имеют переменную длину. Для таких задач, как правило, более уместен нешаблонный подход.
Проверка чисел
При работе с числовым вводом очевидный способ обработки – использовать оператор извлечения для извлечения входных данных в переменную числового типа. Проверяя бит отказа ( failbit ), мы можем определить, ввел ли пользователь число или нет.
Давайте попробуем такой подход:
Если пользователь вводит число, cin.fail() вернет false , и мы перейдем к инструкции break , выходя из цикла. Если пользователь вводит данные, начинающиеся с буквы, cin.fail() вернет true , и мы перейдем к условному выражению.
Однако есть еще один случай, который мы не проверили, и это когда пользователь вводит строку, которая начинается с цифр, но затем содержит буквы (например, » 34abcd56 «). В этом случае начальные числа (34) будут извлечены в переменную age , а остаток строки (» abcd56 «) останется во входном потоке, и бит отказа НЕ будет установлен. Это вызывает две потенциальные проблемы:
- если вы хотите, чтобы это был допустимый ввод, теперь в вашем потоке есть мусор;
- если вы не хотите, чтобы это был допустимый ввод, он не отклоняется (и в вашем потоке есть мусор);
Решим первую проблему. Это просто:
Если вы не хотите, чтобы такие данные были допустимыми, нам придется проделать небольшую дополнительную работу. К счастью, предыдущее решение помогает нам в этом. Мы можем использовать функцию gcount() , чтобы определить, сколько символов было проигнорировано. Если наш ввод был допустимым, gcount() должна вернуть 1 (отброшенный символ новой строки). Если она возвращает больше 1, пользователь ввел что-то, что не было извлечено правильно, и мы должны попросить его ввести новые данные. Ниже показан пример этого:
Проверка чисел в виде строки
В приведенном выше примере потребовалось довольно много работы, чтобы просто получить простое значение! Другой способ обработки числовых входных данных – прочитать их как строку, а затем попытаться преобразовать в переменную числового типа. Следующая программа использует этот метод:
Будет ли этот подход более или менее трудоемким, чем извлечение чисел напрямую, зависит от ваших параметров проверки и ограничений.
Как видите, проверка ввода в C++ – это большая работа. К счастью, многие такие задачи (например, выполнение проверки чисел в виде строки) можно легко превратить в функции, которые можно повторно использовать в самых разных ситуациях.
Источник