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

Вот некоторый код C, пытающийся просто запретить пользователю вводить символ или целое число меньше 0 или больше 23.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    const char *input;
    char *iPtr;
    int count = 0;
    int rows;

    printf("Enter an integer: ");
    scanf("%s", input);
    rows = strtol(input, &iPtr, 0);
    while( *iPtr != '\0') // Check if any character has been inserted
    {
        printf("Enter an integer between 1 and 23: ");
        scanf("%s", input);
    }
    while(0 < rows && rows < 24) // check if the user input is within the boundaries
    {
        printf("Select an integer from 1 to 23: ");
        scanf("%s", input);
    }  
    while (count != rows)  
    {  
        /* Do some stuff */  
    }  
    return 0;  
}

Я сделал это на полпути, и небольшой толчок будет оценен.


person 7kemZmani    schedule 31.12.2012    source источник
comment
Я заметил очевидную проблему в третьей команде scanf, это должно быть scanf(%i, &rows); но все равно код не работает :(   -  person 7kemZmani    schedule 31.12.2012
comment
Рассматривали ли вы выделение памяти для этих scanf вызовов? в настоящее время они считывают адрес, содержащийся в неинициализированном указателе (input), что является неопределенным поведением. Я почти уверен, что если его значения int вы ищете, вы должны использовать %d и сканировать адрес переменной int. Кроме того, проверьте возвращаемые значения ваших scanf вызовов, которые сообщат вам, сколько полей было успешно получено.   -  person WhozCraig    schedule 31.12.2012
comment
Я изменил указатель «input» на массив «char input [100];»   -  person 7kemZmani    schedule 31.12.2012
comment
Почему вы читаете это в текстовый буфер вообще ?? Вы ищете целочисленное значение в [0..23] правильно? Просто отсканируйте до int и проверьте на успешный синтаксический анализ и значение в диапазоне, если только нет каких-то специальных символов, которые вы также хотите получить. Возможно, стоит прочитать больше о scanf()?   -  person WhozCraig    schedule 31.12.2012
comment
есть два типа входных данных, которые я не хочу вводить, символы и целые числа вне диапазона, и поэтому я использовал текстовый буфер.   -  person 7kemZmani    schedule 31.12.2012
comment
@AbdulelahAl-Jeffery: в этом случае вы можете использовать scanf("%d"), чтобы получить целое число, и если пользователь не вводит числовые символы, вы можете снова выполнить сканирование с помощью scanf("%s), чтобы освободить stdin до следующего scanf("%d"). Обратитесь к моему ответу, я обновил его этим   -  person MOHAMED    schedule 31.12.2012
comment
@AbdulelahAl-Jeffery: введите несколько примеров ваших входных данных   -  person MOHAMED    schedule 31.12.2012
comment
удалить while (count != rows);. это блокирует вашу программу   -  person MOHAMED    schedule 31.12.2012
comment
Введите целое число от 1 до 23: 123asd (недействительно), erwea (недействительно), -823 (недействительно), 30 (недействительно), 8 (действительно)   -  person 7kemZmani    schedule 31.12.2012
comment
@AbdulelahAl-Jeffery: я обновил код в своем ответе. и я проверил это, и это работает   -  person MOHAMED    schedule 31.12.2012
comment
Да, это работает, но похоже, что если здесь что-то не так, scanf(%[^\n], input)?! как будто редактор ожидает что-то после символа %?!!! не могли бы вы объяснить этот pice только пожалуйста. спасибо (Джазак Аллах Кир).   -  person 7kemZmani    schedule 31.12.2012
comment
"%[^\n]" означает: ожидается ввод строки, содержащей любые символы, кроме "\n" (новая строка). Это означает прочитать и очистить все содержимое стандартного ввода (берака Аллах фик)   -  person MOHAMED    schedule 31.12.2012
comment
@AbdulelahAl-Jeffery Я использовал функцию clean_stdin вместо scanf("%[^\n]",input), чтобы избежать переполнения буфера input. Пожалуйста, обратитесь к ответу, чтобы увидеть обновление   -  person MOHAMED    schedule 02.01.2013
comment
@MohamedKALLEL Хорошо, теперь мне становится яснее. Я заметил одну вещь: вы избавились от массива input и заменили его на c типа char?! это потому, что мы хотим сохранить как можно больше памяти?   -  person 7kemZmani    schedule 04.01.2013
comment
на вашем месте я бы не стал ставить больше трех условий в цикле while ; Я бы предпочел разбить его на несколько и напечатать соответствующее сообщение, связанное с недопустимым пользовательским вводом. Я думаю, что это улучшит читаемость кода, а также улучшит взаимодействие с пользователем. вы можете поправить меня, если я ошибаюсь. Спасибо брат.   -  person 7kemZmani    schedule 04.01.2013
comment
@AbdulelahAl-Jeffery Существует небольшой риск в scanf("%[^\n]", input), который возникает в том случае, если пользователь вводит строку, превышающую размер входного буфера, поэтому вы получите переполнение буфера. Вот почему я заменил это циклом while и getchar(). getchar() позволяет читать из стандартного ввода 1 символ. и если вы повторите getchar() много раз, пока не получите \n, вы очистите стандартный ввод   -  person MOHAMED    schedule 04.01.2013
comment
@AbdulelahAl-Jeffery Относительно условий цикла while. Я обновил свой ответ для получения дополнительных объяснений. Пожалуйста, обратитесь к ответу. Добро пожаловать Брат   -  person MOHAMED    schedule 04.01.2013


Ответы (5)


Используйте scanf("%d",&rows) вместо scanf("%s",input)

Это позволяет вам напрямую получать целочисленное значение из стандартного ввода без необходимости преобразования в int.

Если пользователь вводит строку, содержащую нечисловые символы, вам необходимо очистить стандартный ввод перед следующим scanf("%d",&rows).

ваш код может выглядеть так:

#include <stdio.h>  
#include <stdlib.h> 

int clean_stdin()
{
    while (getchar()!='\n');
    return 1;
}

int main(void)  
{ 
    int rows =0;  
    char c;
    do
    {  
        printf("\nEnter an integer from 1 to 23: ");

    } while (((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin()) || rows<1 || rows>23);

    return 0;  
}

Пояснение

1)

scanf("%d%c", &rows, &c)

Это означает ожидание от пользователя ввода целого числа и близкого к нему нечислового символа.

Пример 1. Если пользователь введет aaddk, а затем ENTER, сканирование вернет 0. Ничего не перехвачено.

Пример 2. Если пользователь введет 45, а затем ENTER, функция сканирования вернет 2 (2 элемента захвачены). Здесь %d ловит 45, а %c ловит \n

Пример 3. Если пользователь введет 45aaadd, а затем ENTER, функция сканирования вернет 2 (2 элемента захвачены). Здесь %d ловит 45, а %c ловит a

2)

(scanf("%d%c", &rows, &c)!=2 || c!='\n')

В примере 1: это условие равно TRUE, так как scanf возвращает 0 (!=2)

В примере 2: это условие равно FALSE, так как scanf возвращает 2 и c == '\n'

В примере 3: это условие равно TRUE, так как scanf возвращает 2 и c == 'a' (!='\n')

3)

((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin())

clean_stdin() всегда равно TRUE, потому что функция всегда возвращает 1

В примере 1: (scanf("%d%c", &rows, &c)!=2 || c!='\n') равно TRUE, поэтому нужно проверить условие после &&, чтобы clean_stdin() было выполнено, а все условие было TRUE

В примере 2: (scanf("%d%c", &rows, &c)!=2 || c!='\n') равно FALSE, поэтому условие после && не будет проверяться (поскольку каким бы ни был его результат, все условие будет FALSE ), поэтому clean_stdin() не будет быть выполнено, и все условие FALSE

В примере 3: (scanf("%d%c", &rows, &c)!=2 || c!='\n') равно TRUE, поэтому нужно проверить условие после &&, чтобы clean_stdin() было выполнено, а все условие было TRUE

Таким образом, вы можете заметить, что clean_stdin() будет выполняться только в том случае, если пользователь введет строку, содержащую нечисловой символ.

И это условие ((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin()) вернет FALSE только в том случае, если пользователь введет integer и ничего больше.

И если условие ((scanf("%d%c", &rows, &c)!=2 || c!='\n') && clean_stdin()) равно FALSE, а integer находится между и 1 и 23, то цикл while прервется, иначе цикл while продолжится.

person Community    schedule 31.12.2012
comment
Во-первых, нет никакой проверки, чтобы увидеть, действительно ли то, что scanf() сделал (если что-то), действительно заполнило rows. Поэтому вы потенциально проверяете неопределенное значение. вы должны хотя бы подтвердить, что scanf() вернул 1. - person WhozCraig; 31.12.2012
comment
Я использовал функцию clean_stdin вместо scanf("%[^\n]",input), чтобы избежать переполнения буфера input - person MOHAMED; 02.01.2013
comment
Вы можете сделать так, чтобы это также повторялось, когда пользователь только нажимает ввод? - person nyxaria; 12.08.2017

#include <stdio.h>
main()
{
    char str[100];
    int num;
    while(1) {
        printf("Enter a number: ");
        scanf("%[^0-9]%d",str,&num);
        printf("You entered the number %d\n",num);
    }
    return 0;
}

%[^0-9] в scanf() сжирает все, что не находится между 0 и 9. По сути, он очищает входной поток от нецифр и помещает его в str. Что ж, длина нецифровой последовательности ограничена 100. Следующий %d выбирает только целые числа во входном потоке и помещает их в num.

person Swathyprabhu    schedule 22.07.2013

Вы можете создать функцию, которая считывает целое число от 1 до 23 или возвращает 0, если оно не является целым числом.

e.g.

int getInt()
{
  int n = 0;
  char buffer[128];
  fgets(buffer,sizeof(buffer),stdin);
  n = atoi(buffer); 
  return ( n > 23 || n < 1 ) ? 0 : n;
}
person AndersK    schedule 31.12.2012
comment
Что насчет персонажей? Я не ожидаю, что пользователь введет строку или целое число вне диапазона. - person 7kemZmani; 31.12.2012
comment
всегда полезно ничего не предполагать о том, что пишет пользователь. - person AndersK; 31.12.2012

char check1[10], check2[10];
int foo;

do{
  printf(">> ");
  scanf(" %s", check1);
  foo = strtol(check1, NULL, 10); // convert the string to decimal number
  sprintf(check2, "%d", foo); // re-convert "foo" to string for comparison
} while (!(strcmp(check1, check2) == 0 && 0 < foo && foo < 24)); // repeat if the input is not number

Если ввод является числом, вы можете использовать foo в качестве ввода.

person Yonggoo Noh    schedule 18.04.2016

Вам нужно будет повторить свой вызов strtol внутри ваших циклов, где вы просите пользователя повторить попытку. На самом деле, если вы сделаете цикл do { ... } while(...); вместо while, вы не получите такое же поведение повторения вещей дважды.

Вы также должны отформатировать свой код, чтобы можно было увидеть, где код находится внутри цикла, а где нет.

person Mats Petersson    schedule 31.12.2012