В работе со строками в Си нет никакой магии, но вам нужно надеть свою учетную шляпу ... Почему?При работе с вводом вы должны учитывать не только количество символов, которые вы помещаете в свой буфер (или где бы вы ни хранили свой ввод), но вы также должны учитывать символы, которые остаются в вашем потоке ввода!
Это особенно верно при использовании любой функции семейства scanf
для ввода.Зачем?Поскольку при совпадении или сбое ввода обработка (чтение и удаление) символов из буфера ввода (stdin
здесь) останавливается , дальнейшие символы не читаютсяи любой символ, вызывающий сбой , соответствующий , остается непрочитанным в вашем входном потоке, просто ожидая, что вас снова укусят при следующей попытке чтения.Программисты на Си - это тот факт, что некоторые спецификаторы преобразования потребляют начальные пробелы (например, space, tab, newline,...
), а другие - нет.Ваши числовые преобразователи (вместе с "%s"
) занимают первые пробелы, в то время как "%c"
и "%[...]"
этого не делают.
Все это основные причины, по которым новым программистам C рекомендуетсяиспользуйте линейно-ориентированные функции ввода, такие как fgets
или POSIX getline
, для обработки пользовательского ввода (потому что они читают всю строку за раз - включая триал '\n'
), освобождая нового программиста от необходимостидля учета заканчивающихся пробелов или оскорбительных символов, не преобразованных в случае совпадения сбоя ...
Использование fgets
с последующим sscanf
обеспечивает дополнительное преимущество разрешения проверка из (1) чтения ввода;и (2) анализ и преобразование ввода в необходимые значения.
( примечание: единственное предостережение с линейно-ориентированными функциями ввода состоит в том, что они прочитайте и включите завершающий '\n'
в буфер, который они заполняют - так что вам нужно будет "обрезать" завершающий пробел по мере необходимости. Вы не хотите, чтобы на конце строк падали символы '\n'
Вы храните.)
Тем не менее, будут моменты, когда чтение ввода с помощью семейства функций scanf
имеет смысл.В этом нет ничего плохого, если вы проверяете возврат каждый раз и обрабатываете все три возможных условия :
- пользователь нажимает ctrl + d в Linux, чтобы создать руководство
EOF
( ctrl + z при windoze); - вы обрабатываете соответствие или ошибки ввода случаев, включая удаление любых некорректных символов из буфера ввода до следующей попытки чтения;и, наконец,
- у вас хороший ввод (возврат указывает на все ожидаемые преобразования, произошедшие).
В этом нет ничего волшебного, но требуется понимание возможногоусловия ошибок и обработка каждого из них на каждом входе.
В вашем случае, давайте посмотрим на вашу задачу получения количества слов, вводимых пользователем.Здесь вы пытались читать с fgets
(это хорошо!), Но вы не смогли предоставить достаточно памяти для хранения ввода.Когда вы читаете небольшое количество текста от пользователя, вам нужен простой массив с автоматическим типом хранения.Тем не менее, вам необходимо соответствующим образом изменить размер буфера (и НЕ экономить на размере буфера).
Золотого правила не существует, но если бы у меня были пользователи, вводящие текст для преобразования в одно число, то я бы чувствовал себя хорошо с буфером символов 256
(которого более чем достаточно для хранения ввода любогодействительное число, плюс еще 230 нечетных символов для обработки времени, которое кошка делает на клавиатуре и т. д.)
Например, можно было бы принять ввод от пользователя и получить количество вводимых слов.в порядке, аналогичном следующему:
#include <stdio.h>
#define SIZE 10 /* good form defining a constant! */
#define MAXC 256 /* max characters for buffer */
int main (void) {
int nwords = 0, /* number of words to enter */
words = 0, /* each word */
wc = 0; /* word count */
char buf[MAXC] = ""; /* buffer of sufficient size for input */
for (;;) { /* loop continually until valid input or user cancels */
printf ("number of words to enter? [1-%d]: ", SIZE);
if (!fgets (buf, MAXC, stdin)) { /* validate ALL user input */
fputs ("(user canceled input)\n", stderr);
return 1;
}
/* validate length < MAXC - 1 and buf[length-1] == '\n' here */
if (sscanf (buf, "%d", &nwords) != 1) { /* sscanf for conversion */
fputs (" error: invalid conversion to int.\n", stderr);
continue;
}
if (nwords < 1 || SIZE < nwords) /* validate nwords in range */
fprintf (stderr, " %2d out of valid range.\n", nwords);
else /* good input received, break loop */
break;
}
( примечание: ваш цикл while
был преобразован в цикл, который будет непрерывно повторять цикл до тех пор, пока не будет введен действительный ввод между 1 < value < SIZE
. Условие просто заставляет break;
управлять циклом вполучен хороший ввод)
Этот цикл представляет классическое fgets/sscanf
чтение и анализ информации из строки ввода, введенной пользователем.Вы можете разобрать номер из строки любым удобным для вас способом (но не используйте atoi()
- он обеспечивает абсолютно ноль проверку ошибок преобразования).Вы можете использовать strtol
(с надлежащей проверкой), и вы можете просто использовать указатель для обхода буфера, выбирая цифры, преобразовывая их из их ASCII в числовое значение, а затем умножая на 10 и добавляя на ходу.Любой способ хорош, пока вы проверяете, проверяете, проверяете каждую часть операции.
Теперь перейдя к чтению каждого из слов, которые должен ввести пользователь, мы будем игнорировать обычныемудрость и использовать scanf
для выполнения задачи, но мы будем обрабатывать все три возможных случая возврата каждый раз.Мы также добавим счетчик, чтобы отслеживать действительные входные данные, предоставленные пользователем, и выходить из цикла только тогда, когда у нас есть такое количество допустимых целых чисел (или пользователь отменяет, генерируя руководство EOF
).
printf ("\nnumber of words entered: %d\n", nwords);
for (; wc < nwords;) { /* loop continually */
int rtn = 0; /* scanf return */
printf ("please enter a number between 1 and %d: ", SIZE);
rtn = scanf ("%d", &words); /* valdate ALL user input */
if (rtn == EOF) { /* handle EOF (manual) */
fputs ("(user canceled input)\n", stderr);
break;
}
else if (rtn == 0) { /* handle "matching failure" */
int c = getchar(); /* remove offending chars from stdin */
while (c != '\n' && c != EOF)
c = getchar();
fputs (" error: invalid integer input\n", stderr);
continue;
}
else { /* valid integer received */
int c = getchar(); /* remove any extra chars from stdin */
while (c != '\n' && c != EOF)
c = getchar();
if (words < 1 || SIZE < words) /* validate in-range */
fprintf (stderr, " %2d - invalid! (1 < valid < %d)\n",
words, SIZE);
else /* good input, increment word count */
printf (" word[%2d]: %3d\n", ++wc, words);
}
}
Примечание: удаление любых оскорбительных символов из stdin
может быть превращено в удобную функцию, так что вам не придется дублировать циклы каждый раз, когда вам нужно очистить stdin
во время вашегопроцедура ввода.Вы можете заменить его простой функцией, например,
/* helper function to remove any chars left in input buffer */
void empty_stdin()
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
, которая поможет сохранить ваш код в чистоте.Я позволю вам включить это выше.
В целом, вы можете сделать что-то вроде следующего:
#include <stdio.h>
#define SIZE 10 /* good form defining a constant! */
#define MAXC 256 /* max characters for buffer */
int main (void) {
int nwords = 0, /* number of words to enter */
words = 0, /* each word */
wc = 0; /* word count */
char buf[MAXC] = ""; /* buffer of sufficient size for input */
for (;;) { /* loop continually until valid input or user cancels */
printf ("number of words to enter? [1-%d]: ", SIZE);
if (!fgets (buf, MAXC, stdin)) { /* validate ALL user input */
fputs ("(user canceled input)\n", stderr);
return 1;
}
/* validate length < MAXC - 1 and buf[length-1] == '\n' here */
if (sscanf (buf, "%d", &nwords) != 1) { /* sscanf for conversion */
fputs (" error: invalid conversion to int.\n", stderr);
continue;
}
if (nwords < 1 || SIZE < nwords)
fprintf (stderr, " %2d out of valid range.\n", nwords);
else
break;
}
printf ("\nnumber of words entered: %d\n", nwords);
for (; wc < nwords;) { /* loop continually */
int rtn = 0; /* scanf return */
printf ("please enter a number between 1 and %d: ", SIZE);
rtn = scanf ("%d", &words); /* valdate ALL user input */
if (rtn == EOF) { /* handle EOF (manual) */
fputs ("(user canceled input)\n", stderr);
break;
}
else if (rtn == 0) { /* handle "matching failure" */
int c = getchar(); /* remove offending chars from stdin */
while (c != '\n' && c != EOF)
c = getchar();
fputs (" error: invalid integer input\n", stderr);
continue;
}
else { /* valid integer received */
int c = getchar(); /* remove any extra chars from stdin */
while (c != '\n' && c != EOF)
c = getchar();
if (words < 1 || SIZE < words) /* validate in-range */
fprintf (stderr, " %2d - invalid! (1 < valid < %d)\n",
words, SIZE);
else /* good input, increment word count */
printf (" word[%2d]: %3d\n", ++wc, words);
}
}
}
Пример использования / Вывод
$ ./bin/getintstdin
number of words to enter? [1-10]: five, maybe six?
error: invalid conversion to int.
number of words to enter? [1-10]: -2
-2 out of valid range.
number of words to enter? [1-10]: 3
number of words entered: 3
please enter a number between 1 and 10: two? three?
error: invalid integer input
please enter a number between 1 and 10: 2
word[ 1]: 2
please enter a number between 1 and 10: -2
-2 - invalid! (1 < valid < 10)
please enter a number between 1 and 10: 11
11 - invalid! (1 < valid < 10)
please enter a number between 1 and 10: 3
word[ 2]: 3
please enter a number between 1 and 10: 4
word[ 3]: 4
Обратите внимание на все недопустимые входные данные выше и то, как код обрабатывает каждый из них.До тех пор, пока ввод не превышает 255 символов с fgets
, код будет корректно реагировать на входы, которые не являются допустимыми целыми числами (независимо от того, сколько их задано), и он будет реагировать на целочисленные входные данные, выходящие за пределы допустимого диапазона.
Код не намного длиннее, чем код, который вы опубликовали, но он рассматривает возможные условия возникновения ошибок, а затем обрабатывает ошибки.Когда вы все сводите, это и есть кодирование.Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.