Каждый раз, когда вы принимаете пользовательский ввод, вы должны учитывать каждый символ, который остается во входном буфере (stdin
здесь). Это особенно верно при приеме ввода с scanf
(или семейством) из-за того, что scanf
обрабатывает input или , соответствующие сбоям. В любом случае больше не читаются никакие символы , и любые некорректные символы остаются во входном буфере непрочитано - просто ожидаю, что вас снова укусят при следующей попытке чтения (обычно это приводит к бесконечный цикл, если вы принимаете входные данные внутри цикла)
Кроме того, вы должны понимать, как каждый спецификатор преобразования обрабатывает начальный пробел и используется ли начальный пробел указателем (например, числовой спецификатор преобразования и "%s"
) а какие нет (все остальные, особенно "%c"
и "%[...]"
).
Это одна из основных причин, по которой для получения пользовательского ввода рекомендуется использовать линейно-ориентированную функцию , такую как fgets
(с буфером соответствующего размера) или POSIX getline
. И прочитайте и включите завершающий '\n'
в заполненный буфер. Это полностью использует строку, устраняя любую неопределенность, какие дополнительные символы остаются во входном буфере непрочитанными Затем вы можете передать буфер в sscanf
для анализа. Это позволяет независимо проверять как (1) чтение («я получил ввод?»), Так и (2) анализ информации из строки («содержит ли она информацию, которая мне нужна?»).
scanf
можно использовать, если используется правильно. Это означает, что вы несете ответственность за проверку возврата из scanf
каждый раз . Вы должны справиться с тремя условиями
(return == EOF)
пользователь отменил ввод, сгенерировав руководство EOF
, нажав Ctrl + d (или в windows Ctrl + z , но см. CTRL + Z не генерирует EOF в Windows 10 (ранние версии) );
(return < expected No. of conversions)
a соответствие или вход произошел сбой. Для сбоя match вы должны учитывать каждый символ, который останется в вашем входном буфере. (сканирование вперед во входном буфере с чтением и отбрасыванием символов до тех пор, пока не будет найдено '\n'
или EOF
); и наконец
(return == expected No. of conversions)
, указывающий на успешное чтение - тогда вам нужно проверить, соответствует ли входные данные каким-либо дополнительным критериям (например, положительное целое число, положительная плавающая точка, в необходимом диапазоне и т. Д.).
Используя это с вашим кодом, вы могли бы вводить данные следующим образом - непрерывно повторять цикл до получения действительного ввода или до отмены пользователем, генерируя руководство EOF
, например,
#include <stdio.h>
void empty_stdin (void) /* simple helper-function to empty stdin */
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
int main(void)
{
int input = 0,
rtn = 0; /* variable to save scanf return */
// domainEntry *myDomains = buildDomainDB();
for (;;) { /* loop continually until valid input or EOF */
printf ("\nSelect top level domain:\n"
" 1-EDU\n"
" 2-COM\n"
" 3-ORG\n"
" 4-GOV\n"
" 5-MIL\n"
" 6-CN\n"
" 7-COM.CN\n"
" 8.CAN\n\n"
"choice: ");
rtn = scanf (" %d", &input); /* save return */
if (rtn == EOF) { /* user generates manual EOF */
fputs ("(user canceled input.)\n", stderr);
return 1;
}
else if (rtn == 0) { /* matching failure */
fputs (" error: invalid integer input.\n", stderr);
empty_stdin();
}
else if (input < 1 || 8 < input) { /* validate range */
fputs (" error: integer out of range [1-8]\n", stderr);
empty_stdin();
}
else { /* good input */
empty_stdin();
break;
}
}
printf ("\nvalid input: %d\n", input);
}
( примечание об использовании вспомогательной функции empty_stdin()
- и что даже после успешного чтения вы все равно должны empty_stdin()
убедиться, что входной буфер пуст и подготовлен для следующего пользователя вход - что бы это ни было)
Пример использования / вывода
$ ./bin/getintmenu
Select top level domain:
1-EDU
2-COM
3-ORG
4-GOV
5-MIL
6-CN
7-COM.CN
8.CAN
choice: edu
error: invalid integer input.
Select top level domain:
1-EDU
2-COM
3-ORG
4-GOV
5-MIL
6-CN
7-COM.CN
8.CAN
choice: 9
error: integer out of range [1-8]
Select top level domain:
1-EDU
2-COM
3-ORG
4-GOV
5-MIL
6-CN
7-COM.CN
8.CAN
choice: 4
valid input: 4
Если вы делаете свою работу, вы можете успешно использовать scanf
по мере необходимости.
Или вы можете значительно упростить свою жизнь и использовать fgets
, а затем проверить, является ли первый символ в буфере (путем простой разыменования указателя) допустимым выбором меню, например,
#include <stdio.h>
#define MAXC 1024 /* read buffer max characters */
int main (void) {
int input = 0;
char buf[MAXC];
// domainEntry *myDomains = buildDomainDB();
for (;;) { /* loop continually until valid input or EOF */
fputs ("\nSelect top level domain:\n"
" 1-EDU\n"
" 2-COM\n"
" 3-ORG\n"
" 4-GOV\n"
" 5-MIL\n"
" 6-CN\n"
" 7-COM.CN\n"
" 8.CAN\n\n"
"choice: ", stdout);
if (!fgets (buf, MAXC, stdin)) {
fputs ("(user canceled input.)\n", stderr);
return 1;
}
if (*buf < '1' || '8' < *buf) { /* check 1st char, validate range */
fputs (" error: invalid input\n", stderr);
continue;
}
input = *buf - '0'; /* convert char to integer */
break;
}
printf ("\nvalid input: %d\n", input);
}
Конечно, если ключ застрял и пользователь вводит более 1023
символов - символы останутся во входном буфере. Но простой тест на то, является ли последний символ '\n'
и, если нет, были ли прочитаны символы MAXC - 1
, даст вам знать, так ли это. Ваш выбор, но fgets
обеспечивает гораздо более простую реализацию. Просто помните - не экономьте на размере буфера . Я предпочел бы иметь буфер на 10 000 байтов слишком длинным, чем 1-байтовый слишком короткий ....