Проблемы с cin.getline(), не принимающим ввод

Я использую cin.getline() для хранения пользовательского ввода в массиве символов и пытаюсь проанализировать ввод, чтобы разрешить ввод только чисел от 1 до 4. Все работает нормально при определенных обстоятельствах: правильный ввод вводится с первой попытки, ИЛИ вводятся 2 или менее символов, а после вводится правильный ввод. Пример ниже.

[Expected behavior]
Enter input: 1 [ENTER]
Input accepted

[Expected behavior]
Enter input: rw [ENTER]
Incorrect input. Please try again.
Enter input: 1 [ENTER]
Input accepted

[Unexpected behavior]
Enter Input: rtw [ENTER]
Incorrect input. Please try again.
Enter Input: 1 [ENTER]
Incorrect input. Please try again.
Enter input: 1 [ENTER]
Incorrect input. Please try again.
[This will continue indefinitely]

Я пробовал все, от очистки входного буфера до сброса массива символов до нулевых терминаторов в попытке увидеть, хранит ли он все еще значения из предыдущего ввода (например, в неожиданном поведении, если «tw» каким-то образом все еще был в памяти) . Я думаю, что у меня может быть проблема, похожая на это обсуждение, но я не уверен на 100%. Когда я пытаюсь очистить буфер ввода, он ожидает второго набора ввода, и я не знаю, почему. Когда я печатаю результаты inputLength после запуска "неожиданного поведения", он показывает, что в массиве все еще есть 2 или 3 символа, когда я ввел только 1. При удалении cin.clear()/cin.ignore() , второй вход не нужен, но тогда происходит описанное выше поведение. Я ценю любую помощь.

Я разместил соответствующий код ниже.

char* ValidateInput() {
    const int maxInput = 25;
    char userInput[maxInput] = { "\0" };
    int inputLength = 0;
    bool correctInputBool = false;

    while (!correctInputBool) {
        // subtract 1 to allow for null terminator at end of array
        cin.getline(userInput, (maxInput - 1), '\n');


        // I have tried both versions of cin.ignore() below, but neither works
        cin.clear();
        cin.ignore(numeric_limits<streamsize>::max(), '\n');
        //cin.ignore(numeric_limits<streamsize>::max());



        // calculate how many characters user entered
        inputLength = sizeOfCharArray(userInput, maxInput);

        // I do other things here, there isn't a problem with this. For now, assume all 1-character input is acceptable
        if (inputLength == 1) {
            cout << "Correct input." << endl;
            correctInputBool = true;
        }

        if (!correctInputBool) {
            cout << "Sorry, that input is incorrect. Please try again." << endl;
            cout << "Please enter a number between 1 and 4." << endl;
        }
        return userInput;
    }

int sizeOfCharArray(char input[], int maxSize) {
    // all values in input are set to "\0", so count all characters that are not null
    int userSize = 0;
    for (int index = 0; index < maxSize; index++) {
        if (input[index] != '\0') {
            userSize++;
        }
    }
    return userSize;
}

РЕДАКТИРОВАТЬ: я заметил, что когда я ввожу более 3 символов, следующий запуск всегда будет сбрасывать inputLength на одно значение. Ввод 9 символов уменьшается до 8 при повторном запросе ввода, даже если было введено только 1.


person Community    schedule 22.01.2020    source источник
comment
Используете ли вы cin >> в своем коде так же, как cin.getline()? Или любые другие форматированные извлечения из этого потока?   -  person Yksisarvinen    schedule 22.01.2020
comment
Кроме того, *"\0" активирует мои чувства UB (хотя я не уверен в этом на 100%). Вместо этого вы хотели '\0' (один символ).   -  person Yksisarvinen    schedule 22.01.2020
comment
@Yksisarvinen, на данный момент я использую только cin.getline(). И спасибо за исправление, у меня в коде было "\0", но я добавил * после того, как компилятор наорал на меня. Я тоже менял выше.   -  person    schedule 22.01.2020
comment
Для меня я не могу воспроизвести неожиданное поведение.   -  person rjhcnf    schedule 22.01.2020
comment
@rjhcnf, я скопировал свой проект в другой редактор (codeblocks вместо Visual Studio), и проблема осталась. Когда я ввожу более 3 символов, следующий запуск всегда будет уменьшать inputLength на одно значение. Ввод 9 символов уменьшается до 8 при повторном запросе ввода, даже если было введено только 1.   -  person    schedule 22.01.2020
comment
В вашем коде есть несколько опечаток, например, отсутствует точка с запятой после `cout ‹‹ Правильный ввод.` И ValidateInput отсутствует закрывающая }. Может быть, это просто неуместная скобка, потому что прямо сейчас while (!correctInputBool) { всегда будет встречаться с return userInput;, поэтому цикл не имеет смысла в этой форме.   -  person churill    schedule 22.01.2020
comment
Не могли бы вы рассказать, как вы вызываете функцию ValidInput? Это вызывается в цикле while?   -  person rjhcnf    schedule 22.01.2020
comment
Кроме того, есть ли причина, по которой вы не можете или не хотите использовать std::getline и std::string?   -  person churill    schedule 22.01.2020
comment
@churill Я разобрался со своей проблемой и разместил свой ответ ниже. Я хотел бы использовать как std::getline, так и std::string, но не могу, так как это домашнее задание. Я вынужден использовать строки в стиле C   -  person    schedule 22.01.2020


Ответы (2)


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

КСТАТИ. Ваш текущий код не компилируется. Я предполагаю, что у вас есть ; после cout << "Correct input." и } между закрывающей фигурной скобкой if и return, иначе ваш пример никогда не зациклится ни разу.

Проблема, которую вы упоминаете в вопросе

Вы не очищаете userInput (и ваш sizeOfCharArray не готов к этому).

Давайте шаг за шагом с вашим кодом:

  1. userInput[maxInput] = { "\0" }; //userInput contains all null characters
  2. Ввод данных пользователем rwt
  3. userInput содержит "rwt\0"
  4. Ваш код правильно видит его как недопустимый ввод и снова запрашивает ввод
  5. Ввод данных пользователем 1
  6. userInput перезаписывается введенной пользователем строкой, но она не очищается заранее. Теперь он содержит 1\0t\0
  7. sizeOfCharArray вычисляет все ненулевые символы и возвращает 2.
  8. Ваш цикл продолжает запрашивать ввод.

Вы возвращаете адрес локальной переменной

После ValidateInput массив userInput мертв. Ушел навсегда. И вы возвращаете адрес в мертвый массив, память, которую компилятор может использовать как угодно.

Ваш код слишком запутан

Эту проблему часто недооценивают, но простой код = легкое чтение = меньше ошибок.

Вам нужно целое число, верно? Итак, как насчет чтения целого числа из ввода?

int GetInput() {
    int result {};
    while (true) { //infinite loop
        cin >> result;

        if(!cin.good()) {
            cout << "Input wasn't a number, please try again.\n";
            cin.clear(); //clear flags that were raised
            cin.ignore(numeric_limits<streamsize>::max(), '\n'); // skip any input remaining in the stream
        } else if (!InputIsValid(result)) {
            cout << "Input not in 1-4 range, please try again.\n";
        } else {
            return result;
        }
    }
}

bool InputIsValid(int input) {
    return input >= 1 && input <= 4;
}

std::cin поднимет свой fail бит, если ему не удастся извлечь запрошенный тип (в данном случае int) и переменная будет обнулена (начиная с C++11). Если установлен бит fail, метод good() вернет false (т. е. поток не в хорошем состоянии). На следующей итерации мы очищаем флаги и все оставшиеся входные данные из потока.
Вы также можете проверить допустимый диапазон целого числа в цикле (здесь это делается как отдельная функция).

Мы выходим из цикла, используя оператор return. Если все правильно (т. е. потоку удалось прочитать правильный тип ввода, и ввод находится в допустимом диапазоне), мы возвращаем окончательное значение пользователю.


Относительно проблем с кавычками: Одинарные и двойные кавычки в C или C++. Пожалуйста, для вашего собственного психического здоровья, не добавляйте символы случайным образом, пока компилятор не перестанет показывать ошибки, это никогда не является хорошей идеей. Компилятор — ваш друг, он поможет вам обнаружить проблемы, но только если вы поможете ему это сделать.

person Yksisarvinen    schedule 22.01.2020

Я смог использовать визуальную студию и посмотреть переменные inputLength и userInput. inputLength действительно уменьшилось только на 1 значение, потому что в конец ввода был добавлен нулевой терминатор. Из-за этого

userInput = "asdf" // with the reminaing values being '\0'
inputLength = 4

userInput = "2'\0'df" // with the following being held in memory: 2 '\0' df '\0' '\0' . . .
inputLength = 3

Когда я пытался напечатать значения userInput, я видел только 2 из-за нулевого терминатора; хотя значения все еще были там, они не печатались, потому что компилятор увидел '\0' и понял, что после этого ничего не было. В результате, когда я вызывал sizeOfCharArray, подсчитывались все значения, которые не были нулевыми терминаторами, в том числе и из предыдущего ввода.

Мой обновленный код ниже.

char* ValidateInput() {
    const int maxInput = 25;
    char userInput[maxInput] = { "\0" };
    int inputLength = 0;
    bool correctInputBool = false;

    while (!correctInputBool) {         
        // updated section
        for (int index = 0; index < maxInput; index++) {
            userInput[index] = '\0';
        }
        // subtract 1 to allow for null terminator at end of array
        cin.getline(userInput, (maxInput - 1), '\n');

        // calculate how many characters user entered
        inputLength = sizeOfCharArray(userInput, maxInput);

        // I do other things here, there isn't a problem with this. For now, assume all 1-character input is acceptable
        if (inputLength == 1) {
            cout << "Correct input."
            correctInputBool = true;
        }

        if (!correctInputBool) {
            cout << "Sorry, that input is incorrect. Please try again." << endl;
            cout << "Please enter a number between 1 and 4." << endl;
        }
        return userInput;
    }

int sizeOfCharArray(char input[], int maxSize) {
    // all values in input are set to "\0", so count all characters that are not null
    int userSize = 0;
    for (int index = 0; index < maxSize; index++) {
        if (input[index] != *"\0") {
            userSize++;
        }
    }
    return userSize;
}
person Community    schedule 22.01.2020
comment
Это не должно работать, вы устанавливаете весь буфер в \0 сразу после его чтения. Обновленный раздел наверное должен быть раньше cin.getline(...)? - person churill; 22.01.2020
comment
@churill да, моя ошибка. Это было исправлено сейчас - person ; 22.01.2020