Я хочу создать код, который станет основой для проверки ввода всего моего будущего кода.
Я испытываю трудности в тот момент, когда прошу пользователя повторно ввести свои данные. Я не понимаю, почему мой std::cin >> var
в if (is_var_an_int == false)
утверждении не работает. Его просто игнорируют, и я не могу его изменить, а если бы я использовал цикл while
, он просто печатался бы бесконечно.
Я хочу понять, почему мое утверждение std::cin
не работает.
Вот код, который я написал:
#include <iostream>
int main()
{
std::cout << "Demo on !" << std::endl;
int var{};
bool is_var_an_int{};
std::cout << "Var: "; std::cin >> var;
if (std::cin.good())
{
is_var_an_int = true;
std::cin.clear();
}
else
{
is_var_an_int = false;
std::cin.clear();
}
if (is_var_an_int == false)
{
std::cout << "in the loop" << "\n";
std::cin.clear();
std::cout << "New var: "; std::cin >> var;
if (std::cin.good())
{
is_var_an_int = true;
std::cin.clear();
}
else
{
is_var_an_int = false;
std::cin.clear();
}
}
std::cout << is_var_an_int;
}
Знайте, что я только начал изучать C++.
Я пытался выяснить, есть ли способ определить, не является ли ввод пользователя тем же типом, что и ожидаемый тип. Это решается с помощью std::cin.good()
. Но теперь, после того как пользователь ввел ввод типа "hqjdhbqj"
, я хочу, чтобы мой код сказал пользователю, что введенные им данные не соответствуют типу и ему (пользователю) необходимо повторить попытку.
clear()
только очищает флаг ошибки. В следующий раз, когда вы будете читать, вы снова прочтете с той позиции, где это не удалось, и снова потерпите неудачу.
Чтобы избежать повторения одной и той же ошибки снова и снова, вам также необходимо переместить позицию чтения вперед во входном потоке.
Вам нужно гораздо меньше кода, чем вы думаете.
Что-то вроде этого должно сделать:
#include <limits>
// ...
std::cout << "Enter an integer: ";
while (!(std::cin >> var)) // This is true when reading fails.
{
// Clear error flag.
std::cin.clear();
// Discard rest of line. (Write a function of your own that does this.)
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
// Prompt again.
std::cout << "Please enter an integer: ";
}
// When you reach this point you know that 'var' was successfully read.
std::cin
не возвращает значение, вам нужно проверить наличие ошибки.
@OldBoy Оператор >>
возвращает ссылку на поток.
@OldBoy См. std::basic_ios::operator bool
Ну, я извиняюсь, но это очень странно. Я пробовал этот код ранее, и он ничего не прочитал. Только что попробовал еще раз, все работает. Итак, я думаю, что я что-то неправильно напечатал раньше, хотя был уверен, что скопировал ваш код.
@NathanPierson, очевидно, моя вина.
Я не знаю почему, но у меня та же проблема, что и раньше, она просто зацикливается, когда я делаю нецелочисленный ввод, как раньше. Он не позволяет мне вводить данные...
@AndhumeMoudjahidi, тогда вы, вероятно, неправильно применили код к своему проекту. Вам следует использовать отладчик вашего компилятора, чтобы просмотреть код и точно увидеть, что он делает и где он ведет себя не так, как ожидалось.
@molbdnilo some_big_number
должно быть std::numeric_limits<std::streamsize>::max()
, так как ignore()
имеет специальную обработку для этого конкретного значения. Не предполагайте, какой может быть максимальная длина линии терминала.
Проверка ввода часто выполняется с использованием специальной функции.
Такая функция, как get_int
(ниже), перехватывает две распространенные ошибки ввода.
// main.cpp
#include <iostream> // cin, cout, streamsize
#include <limits> // numeric_limits
#include <string> // string
int get_int(
std::string const& prompt,
int const min,
int const max)
{
int n{};
for (;;) // "infinite" loop
{
std::cout << prompt;
if (!(std::cin >> n))
{
std::cout << "Invalid entry. Please reenter.\n\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
else if (n < min || n > max)
{
std::cout << "Invalid entry. Please reenter.\n"
<< "Entries must be between " << min << " and " << max << ".\n\n";
}
else
{
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
break; // break out of for-loop
}
}
return n;
}
int main() {
std::cout << "Example 1\n\n";
int a{ get_int("Enter an integer between 1 and 10: ", 1, 10) };
std::cout << "\nYour entry: " << a << "\n\n";
std::cout << "---------------------------------------------\n"
"Example 2\n\n";
int b{ get_int("Enter an integer between -5 and 5: ", -5, 5) };
std::cout << "\nYour entry: " << b << "\n\n";
std::cout << "---------------------------------------------\n"
"Example 3\n\n";
int c{ get_int("Enter an integer between 100 and 200: ", 100, 200) };
std::cout << "\nYour entry: " << c << "\n\n";
return 0;
}
// end file: main.cpp
Функция get_int
имеет три параметра: prompt
, min
и max
. При вызове get_int
аргументы для трех параметров предоставляются в круглых скобках.
Функция get_int
отображает prompt
, а затем пытается ввести переменную n
(int
) от пользователя. Значение, введенное пользователем, должно быть допустимым целым числом между min
и max
, в противном случае функция отображает сообщение об ошибке и возвращается назад, предлагая пользователю повторить попытку.
Значение, возвращаемое функцией get_int
, гарантированно будет допустимым целым числом между min
и max
.
Например, в функции main
переменная a
инициализируется следующим образом:
int a{ get_int("Enter an integer between 1 and 10: ", 1, 10) };
Аргументом для параметра prompt
является "Enter an integer between 1 and 10: "
Аргументом для параметра min
является 1
Аргументом для параметра max
является 10
for(;;)
— бесконечный цикл. Это повторяется до тех пор, пока оператор прерывания не приведет к выходу.
Если cin
ожидает int
, а пользователь вводит нечисловую (текстовую) запись, cin
будет переведен в состояние сбоя. После этого все последующие операции над cin
завершится неудачно. cin.clear()
восстанавливает cin
до хорошего состояния, чтобы последующие операции автоматически не завершались сбоем.
После нечислового ввода недостаточно просто очистить cin
. Вы также должны удалить недопустимую запись, все еще находящуюся во входном потоке. Именно это и делает cin.ignore
. Используемая здесь версия отбрасывает все символы до (включительно) символа новой строки ('\n'
), который отмечает конец строки. Подробности смотрите в CppReference.
if (!(std::cin >> n))
— тест, который обнаруживает нечисловые текстовые записи. По сути, там говорится «если (cin не удается ввести переменную n
)», но объяснение имеет нюансы. Подвыражение std::cin >> n
делает две вещи: (1) пытается ввести переменную n
и (2) возвращает ссылку на std::cin
в окружающее выражение. Итак, !(std::cin >> n)
оценивается как !(std::cin)
. Не вдаваясь в подробности операторов преобразования , определенных для std::cin
, последнее выражение !(std::cin)
преобразуется в !(!std::cin.fail())
. Два логических отрицания (!
) компенсируются, поэтому у вас остается std::cin.fail()
и оператор if, который можно прочитать как «if (cin не работает при вводе переменной n
)».
Функция get_int
также вызывает cin.ignore
после успешного входа. Без него символ новой строки, завершающий допустимую запись, остался бы во входном потоке. В большинстве случаев это не проблема. Однако если программа также использует std::getline
, отказ от новой строки позволяет последующему вызову std::getline
вести себя ожидаемым образом. Для получения дополнительной информации см. множество публикаций на Stack Overflow, в которых объясняется, как смешивать cin >> value
и std::getline
в одной программе.
Вот результат пробного запуска.
Example 1
Enter an integer between 1 and 10: Huh?
Invalid entry. Please reenter.
Enter an integer between 1 and 10: 20
Invalid entry. Please reenter.
Entries must be between 1 and 10.
Enter an integer between 1 and 10: 7
Your entry: 7
---------------------------------------------
Example 2
Enter an integer between -5 and 5: 3.14
Your entry: 3
---------------------------------------------
Example 3
Enter an integer between 100 and 200: 101garbage
Your entry: 101
Пример 1 работал отлично. Нечисловая запись Huh?
была успешно перехвачена, как и запись, выходящая за пределы допустимого диапазона 20
.
Примеры 2 и 3 демонстрируют один и тот же недостаток. cin >> value
радостно останавливается всякий раз, когда находит символ, который не может быть частью допустимой записи. В примере 2 он останавливается, когда встречает точку .
в 3.14
. Это потому, что переменная типа int
не может содержать точку. Аналогично, в примере 3 он останавливается, когда видит следующий за g
101
.
Обратите внимание, что функция get_int
вызывает cin.ignore
после успешного входа. Именно поэтому .14
был отброшен в примере 2, не вызвав последующего сбоя. То же самое касается garbage
в примере 3.
std::getline
Если вы используете std::getline
для ввода всей строки, вы можете проверить символы, следующие за данной записью. Это позволяет вам перехватывать такие записи, как 3.14
в примере 2 и 101garbage
в примере 3.
Следующая версия функции get_int
требует, чтобы строка содержала ровно одно целое число. Никакие другие символы не допускаются. Поскольку он использует более продвинутые библиотечные функции, такие как std::optional
и std::from_chars
, а также потому, что этот ответ длился достаточно долго, я оставляю ОП поиск деталей его работы.
В своей работе я использую шаблонную версию, которая вводит числа любого арифметического типа.
// main.cpp
#include <charconv> // from_chars
#include <iostream> // cin, cout
#include <optional> // optional, nullopt
#include <stdexcept> // runtime_error
#include <string> // string, getline
#include <string_view> // string_view
#include <system_error> // errc
auto trim_whitespace_view(std::string_view sv) noexcept -> std::string_view
{
// Trim leading and trailing whitespace from string_view `sv`.
auto const first{ sv.find_first_not_of(" \f\n\r\t\v") };
if (first == std::string_view::npos)
return {};
auto const last{ sv.find_last_not_of(" \f\n\r\t\v") };
enum : std::string_view::size_type { one = 1u };
return sv.substr(first, (last - first + one));
}
auto to_optional_int(std::string_view sv) noexcept -> std::optional<int>
{
sv = trim_whitespace_view(sv);
auto const end{ sv.data() + sv.size() };
int n;
auto [ptr, ec] = std::from_chars(sv.data(), end, n);
return (ec == std::errc{} && ptr == end) ? std::optional<int>{ n } : std::nullopt;
}
int get_int(
std::string const& prompt,
int const min,
int const max)
{
for (;;) // "infinite" loop
{
std::cout << prompt;
if (std::string s; !std::getline(std::cin, s))
{
throw std::runtime_error("Function `get_int`: `std::getline` failed.");
}
else if (auto const sv{ trim_whitespace_view(s) }; sv.empty())
{
std::cout << "Invalid entry. Please reenter.\n"
"Entries cannot be blank.\n\n";
}
else if (auto const o{ to_optional_int(sv) }; !o)
{
std::cout << "Invalid entry. Please reenter.\n\n";
}
else if (auto const n{ *o }; n < min || n > max)
{
std::cout << "Invalid entry. Please reenter.\n"
<< "Entries must be between " << min << " and " << max << ".\n\n";
}
else
{
return n;
}
}
}
int main() {
std::cout << "Example 1\n\n";
int a{ get_int("Enter an integer between 1 and 10: ", 1, 10) };
std::cout << "\nYour entry: " << a << "\n\n";
std::cout << "---------------------------------------------\n"
"Example 2\n\n";
int b{ get_int("Enter an integer between -5 and 5: ", -5, 5) };
std::cout << "\nYour entry: " << b << "\n\n";
std::cout << "---------------------------------------------\n"
"Example 3\n\n";
int c{ get_int("Enter an integer between 100 and 200: ", 100, 200) };
std::cout << "\nYour entry: " << c << "\n\n";
return 0;
}
// end file: main.cpp
Example 1
Enter an integer between 1 and 10: Huh?
Invalid entry. Please reenter.
Enter an integer between 1 and 10: 20
Invalid entry. Please reenter.
Entries must be between 1 and 10.
Enter an integer between 1 and 10: 7
Your entry: 7
---------------------------------------------
Example 2
Enter an integer between -5 and 5: 3.14
Invalid entry. Please reenter.
Enter an integer between -5 and 5: 3
Your entry: 3
---------------------------------------------
Example 3
Enter an integer between 100 and 200: 101garbage
Invalid entry. Please reenter.
Enter an integer between 100 and 200: 101
Your entry: 101
Если вы хотите прочитать
int
от пользователя с полной проверкой ввода, то вы можете взглянуть на функциюget_int_from_user
из этого моего ответа на другой вопрос.