Каков наилучший способ проверки ввода в C ++ с помощью cin? - PullRequest
8 голосов
/ 13 февраля 2009

Мой брат недавно начал изучать C ++. Он рассказал мне о проблеме, с которой столкнулся при попытке проверить ввод в простой программе. У него было текстовое меню, в котором пользователь вводил целое число choice, если он вводил неправильный выбор, ему было бы предложено ввести его снова (цикл do while). Однако, если пользователь введет строку вместо int, код будет нарушен. Я прочитал различные вопросы о stackoverflow и велел ему переписать свой код следующим образом:

#include<iostream>
using namespace std;

int main()
{
    int a;
    do
    {
    cout<<"\nEnter a number:"
    cin>>a;
        if(cin.fail())
        {
            //Clear the fail state.
            cin.clear();
            //Ignore the rest of the wrong user input, till the end of the line.
            cin.ignore(std::numeric_limits<std::streamsize>::max(),\
                                                    '\n');
        }
    }while(true);
    return 0;
}

Хотя это работало нормально, я также попробовал несколько других идей:
1. Используя блок try catch. Это не сработало. Я думаю, это потому, что исключение не возникает из-за неправильного ввода. 2. Я попробовал if(! cin){//Do Something}, который тоже не работал. Я еще не понял этого.
3. В-третьих, я попытался ввести строку фиксированной длины, а затем проанализировать ее. Я бы использовал atoi (). Являются ли эти стандарты совместимыми и портативными? Должен ли я написать свою собственную функцию синтаксического анализа?
4. Если написать класс, который использует cin, но динамически выполняет такой тип обнаружения ошибок, возможно, путем определения типа входной переменной во время выполнения, будет ли он иметь слишком много накладных расходов? Это вообще возможно?

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

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

РЕДАКТИРОВАТЬ : Я вижу, что litb ответил на одно из моих предыдущих изменений. Я опубликую этот код здесь для справки.

#include<iostream>
using namespace std;

int main()
{
    int a;
    bool inputCompletionFlag = true;
    do
    {
    cout<<"\nEnter a number:"
    cin>>a;
        if(cin.fail())
        {
            //Clear the fail state.
            cin.clear();
            //Ignore the rest of the wrong user input, till the end of the line.
            cin.ignore(std::numeric_limits<std::streamsize>::max(),\
                                                    '\n');
        }
        else
        {
            inputCompletionFlag = false;
        }
    }while(!inputCompletionFlag);
    return 0;
}

Этот код не работает при вводе, например "1asdsdf". Я не знал, как это исправить, но Литб опубликовал отличный ответ. :)

Ответы [ 8 ]

17 голосов
/ 13 февраля 2009

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

42crap

Где после номера идут нечисловые символы. Если вы прочитали всю строку, а затем проанализировали ее и выполнили соответствующие действия, возможно, вам потребуется изменить способ работы вашей программы. Если ваша программа до сих пор считывает ваш номер из разных мест, вам нужно поместить одно центральное место, которое анализирует одну строку ввода и принимает решение о действии. Но, возможно, это тоже хорошо - так что вы могли бы улучшить читаемость кода, разделив вещи так: I nput - P rocessing - O utput

В любом случае, вот как вы можете отклонить число-номер выше. Прочитайте строку в строку, а затем проанализируйте ее с помощью stringstream:

std::string getline() {
  std::string str;
  std::getline(std::cin, str);
  return str;
}

int choice;
std::istringstream iss(getline());
iss >> choice >> std::ws;
if(iss.fail() || !iss.eof()) {
  // handle failure
}

Съедает все оставшиеся пробелы. Когда он достигает конца файла в потоке строки при чтении целого или конечного пробела, он устанавливает бит eof, и мы проверяем это. Если ему не удалось прочитать какое-либо целое число, то будет установлен бит сбоя или сбой.

Более ранние версии этого ответа использовали std::cin напрямую - но std::ws не будет хорошо работать вместе с std::cin, подключенным к терминалу (вместо этого он будет блокировать ожидание ввода пользователем чего-либо), поэтому мы используем stringstream для чтения целого числа.


Отвечая на некоторые ваши вопросы:

Вопрос: 1. Использование блока try catch. Это не сработало. Я думаю, это потому, что исключение не вызвано из-за неверного ввода.

Ответ: Ну, вы можете указать потоку генерировать исключения, когда вы что-то читаете. Вы используете функцию istream::exceptions, которая сообщает, для какого типа ошибки вы хотите создать исключение:

iss.exceptions(ios_base::failbit);

Я никогда не использовал его. Если вы сделаете это на std::cin, вам нужно будет помнить, чтобы восстановить флаги для других читателей, которые полагаются на то, что они не выбрасывают. Найти способ проще - просто использовать функции fail , bad , чтобы запросить состояние потока.

Вопрос: 2. Я попробовал if(!cin){ //Do Something }, который тоже не работал. Я еще не понял этого.

Ответ: Это может быть связано с тем, что вы дали ему что-то вроде "42crap". Для потока это полностью допустимый ввод при выполнении извлечения в целое число.

Вопрос: 3. В-третьих, я попытался ввести строку фиксированной длины, а затем проанализировать ее. Я бы использовал atoi (). Являются ли эти стандарты совместимыми и портативными? Должен ли я написать свою собственную функцию синтаксического анализа?

Ответ: Atoi соответствует стандарту. Но это не хорошо, когда вы хотите проверить на наличие ошибок. В отличие от других функций, проверка ошибок не производится. Если у вас есть строка и вы хотите проверить, содержит ли она число, сделайте это, как в приведенном выше исходном коде.

Существуют C-подобные функции, которые могут читать непосредственно из C-строки. Они существуют для взаимодействия со старым, унаследованным кодом и написания быстродействующего кода. Следует избегать их в программах, потому что они работают довольно низкоуровнево и требуют использования сырых голых указателей. По своей природе они не могут быть улучшены для работы с пользовательскими типами. В частности, речь идет о функции "strtol" (строка-в-long), которая в основном является atoi с проверкой ошибок и способностью работать с другими базами (например, hex).

Вопрос: 4. Если я напишу класс, который использует cin, но динамически выполняет этот вид обнаружения ошибок, возможно, путем определения типа входной переменной во время выполнения, будет ли у него слишком много накладных расходов? Это вообще возможно?

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

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

12 голосов
/ 13 февраля 2009

Это то, что я делаю с C, но, вероятно, это применимо и к C ++.

Введите все как строку.

Тогда, и только тогда, разберите строку на то, что вам нужно. Иногда лучше написать свой код, чем пытаться склонить кого-то к своей воле.

4 голосов
/ 13 февраля 2009
  • Чтобы получить исключения с iostreams , необходимо установить соответствующий флаг исключения для потока.
  • И я бы использовал get_line , чтобы получить всю строку ввода и затем обработать ее соответственно - используйте lexical_cast, регулярные выражения (например, Boost Regex или Boost Xpressive , разобрать его с помощью Boost Spirit или просто использовать какую-то подходящую логику
3 голосов
/ 13 февраля 2009

То, что я хотел бы сделать, имеет два аспекта: во-первых, попробуйте проверить входные данные и извлечь данные с помощью регулярного выражения, если входные данные несколько не тривиальны. Это может быть очень полезно, даже если ввод представляет собой просто последовательность чисел.

Затем я хотел бы использовать boost :: lexical_ cast , который может вызвать исключение bad_ lexical_ cast, если вход не может быть преобразован.

В вашем примере:

std::string in_str;
cin >> in_str;

// optionally, test if it conforms to a regular expression, in case the input is complex

// Convert to int? this will throw bad_lexical_cast if cannot be converted.
int my_int = boost::lexical_cast<int>(in_str);
2 голосов
/ 13 февраля 2009

Как насчет сочетания различных подходов:

  1. Получить ввод из std::cin, используя std::getline(std::cin, strObj), где strObj - это std::string объект.

  2. Используйте boost::lexical_cast для выполнения лексического перевода от strObj до целого числа со знаком или без знака наибольшей ширины (например, unsigned long long или чего-то подобного)

  3. Используйте boost::numeric_cast, чтобы привести целое число к ожидаемому диапазону.

Вы можете просто получить ввод с помощью std::getline, а затем вызвать boost::lexical_cast для соответственно узкого целочисленного типа, в зависимости от того, где вы хотите перехватить ошибку. Преимущество трехэтапного подхода заключается в приеме любых целочисленных данных, а затем в отдельности выявляются ошибки сужения.

2 голосов
/ 13 февраля 2009

Забудьте об использовании форматированного ввода (оператор >>) непосредственно в реальном коде. Вам всегда нужно будет читать необработанный текст с помощью std :: getline или аналогичного, а затем использовать свои собственные процедуры синтаксического анализа ввода (которые могут использовать оператор >>) для анализа ввода.

1 голос
/ 13 февраля 2009

Одна вещь, которая еще не упоминалась, это то, что обычно важно проверить, работала ли операция cin >> перед использованием переменной, которая предположительно получила что-то из потока.

Этот пример похож на ваш, но делает этот тест.

#include <iostream>
#include <limits>
using namespace std;
int main()
{
   while (true)
   {
      cout << "Enter a number: " << flush;
      int n;
      if (cin >> n)
      {
         // do something with n
         cout << "Got " << n << endl;
      }
      else
      {
         cout << "Error! Ignoring..." << endl;
         cin.clear();
         cin.ignore(numeric_limits<streamsize>::max(), '\n');
      }
   }
   return 0;
}

Это будет использовать обычный оператор >> семантика; сначала он пропустит пробел, затем попытается прочитать столько цифр, сколько сможет, а затем остановится. Таким образом, «42crap» даст вам 42, а затем пропустите «дерьмо». Если это не то, что вы хотите, то я согласен с предыдущими ответами, вы должны прочитать его в строку и затем проверить его (возможно, используя регулярное выражение - но это может быть излишним для простой числовой последовательности).

1 голос
/ 13 февраля 2009

Я согласен с Паксом, самый простой способ сделать это - прочитать все как строку, а затем использовать TryParse для проверки ввода. Если он в правильном формате, тогда продолжайте, иначе просто уведомите пользователя и используйте продолжить в цикле.

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