Как я могу гарантировать, что пользователь вводит правильный тип ввода?

Я хочу создать код, который станет основой для проверки ввода всего моего будущего кода.

Я испытываю трудности в тот момент, когда прошу пользователя повторно ввести свои данные. Я не понимаю, почему мой 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", я хочу, чтобы мой код сказал пользователю, что введенные им данные не соответствуют типу и ему (пользователю) необходимо повторить попытку.

Если вы хотите прочитать int от пользователя с полной проверкой ввода, то вы можете взглянуть на функцию get_int_from_user из этого моего ответа на другой вопрос.

Andreas Wenzel 24.04.2024 01:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
87
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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 23.04.2024 16:12

@OldBoy Оператор >> возвращает ссылку на поток.

molbdnilo 23.04.2024 16:27

@OldBoy См. std::basic_ios::operator bool

Nathan Pierson 23.04.2024 16:39

Ну, я извиняюсь, но это очень странно. Я пробовал этот код ранее, и он ничего не прочитал. Только что попробовал еще раз, все работает. Итак, я думаю, что я что-то неправильно напечатал раньше, хотя был уверен, что скопировал ваш код.

OldBoy 23.04.2024 17:10

@NathanPierson, очевидно, моя вина.

OldBoy 23.04.2024 17:12

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

AndhumeMoudjahidi 23.04.2024 18:08

@AndhumeMoudjahidi, тогда вы, вероятно, неправильно применили код к своему проекту. Вам следует использовать отладчик вашего компилятора, чтобы просмотреть код и точно увидеть, что он делает и где он ведет себя не так, как ожидалось.

Remy Lebeau 23.04.2024 18:58

@molbdnilo some_big_number должно быть std::numeric_limits<std::streamsize>::max(), так как ignore() имеет специальную обработку для этого конкретного значения. Не предполагайте, какой может быть максимальная длина линии терминала.

Remy Lebeau 23.04.2024 19:00

Проверка ввода часто выполняется с использованием специальной функции.

Такая функция, как get_int (ниже), перехватывает две распространенные ошибки ввода.

  1. Нечисловые (текстовые) записи
  2. Записи вне диапазона
// 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 он останавливается, когда видит следующий за g101.

Обратите внимание, что функция 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

Другие вопросы по теме