Ответ 1
Существует несколько подходов, в зависимости от того, насколько надежным будет ваш код.
Наиболее простым является использование scanf
с помощью спецификатора преобразования %d
:
while (scanf("%d", &a[i++]) == 1)
/* empty loop */ ;
Спецификатор преобразования %d
сообщает scanf
пропускать все ведущие пробелы и читать до следующего нецифрового символа. Возвращаемое значение - это количество успешных конверсий и назначений. Поскольку мы читаем одно целое значение, возвращаемое значение должно быть 1 при успехе.
Как написано, у этого есть ряд подводных камней. Во-первых, предположим, что ваш пользователь вводит больше чисел, чем ваш массив имеет размер для хранения; если вам повезет, вы сразу же получите нарушение доступа. Если вы этого не сделаете, вы соберете что-то важное, что вызовет проблемы позже (переполнение буфера - распространенный вредоносный код).
Итак, вы хотя бы хотите добавить код, чтобы убедиться, что вы не проходите мимо конца вашего массива:
while (i < ARRAY_SIZE && scanf("%d", &a[i++]) == 1)
/* empty loop */;
Хорошо. Но теперь предположите, что ваш пользователь оживляет нечисловой символ на своем входе, например 12 3r5 67
. Как написано, цикл присваивает 12
a[0]
, 3
- a[1]
, тогда он увидит r
во входном потоке, вернет 0 и выйдет без сохранения ничего в a[2]
. Здесь, где возникает тонкая ошибка, хотя ничто не привязано к a[2]
, выражение i++
все еще оценивается, поэтому вы подумаете, что вы присвоили что-то a[2]
, хотя оно содержит значение мусора. Таким образом, вы можете приостановить приращение i
, пока не узнаете, что успешно прочитали:
while (i < ARRAY_SIZE && scanf("%d", &a[i]) == 1)
i++;
В идеале вы хотели бы отклонить 3r5
вообще. Мы можем прочитать символ сразу после номера и убедиться, что он пробельный; если это не так, мы отклоняем вход:
#include <ctype.h>
...
int tmp;
char follow;
int count;
...
while (i < ARRAY_SIZE && (count = scanf("%d%c", &tmp, &follow)) > 0)
{
if (count == 2 && isspace(follow) || count == 1)
{
a[i++] = tmp;
}
else
{
printf ("Bad character detected: %c\n", follow);
break;
}
}
Если мы получим два успешных конверсии, убедитесь, что follow
является символом пробела - если это не так, мы печатаем ошибку и выходим из цикла. Если мы получим 1 успешное преобразование, это означает, что после номера ввода не было символов (это означает, что мы нажимаем EOF после числового ввода).
В качестве альтернативы мы можем прочитать каждое входное значение в виде текста и использовать strtol
для преобразования, что также позволяет вам выявлять такую же проблему (мой предпочтительный метод):
#include <ctype.h>
#include <stdlib.h>
...
char buf[INT_DIGITS + 3]; // account for sign character, newline, and 0 terminator
...
while(i < ARRAY_SIZE && fgets(buf, sizeof buf, stdin) != NULL)
{
char *follow; // note that follow is a pointer to char in this case
int val = (int) strtol(buf, &follow, 10);
if (isspace(*follow) || *follow == 0)
{
a[i++] = val;
}
else
{
printf("%s is not a valid integer string; exiting...\n", buf);
break;
}
}
НО ЖДИТЕ БОЛЬШЕ!
Предположим, что ваш пользователь является одним из тех скрученных типов QA, которым нравится бросать неприятный ввод в ваш код "просто, чтобы узнать, что происходит", и вводит число, подобное 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
, которое явно слишком велико, чтобы вписаться в любое из стандартных чисел типы. Верьте или нет, scanf("%d", &val)
не будет навязываться этому, и завершит хранение чего-то до val
, но опять же это вход, который вы, вероятно, хотели бы отклонить напрямую.
Если вы разрешаете только одно значение в строке, это становится относительно легко защищаться; fgets
будет хранить символ новой строки в целевом буфере, если есть место, поэтому, если мы не увидим символ новой строки во входном буфере, пользователь наберет то, что дольше, чем мы готовы обработать:
#include <string.h>
...
while (i < ARRAY_SIZE && fgets(buf, sizeof buf, stdin) != NULL)
{
char *newline = strchr(buf, '\n');
if (!newline)
{
printf("Input value too long\n");
/**
* Read until we see a newline or EOF to clear out the input stream
*/
while (!newline && fgets(buf, sizeof buf, stdin) != NULL)
newline = strchr(buf, '\n');
break;
}
...
}
Если вы хотите разрешить несколько значений в строке, например "10 20 30", это становится немного сложнее. Мы могли бы вернуться к чтению отдельных символов из ввода и выполнить проверку работоспособности каждого (предупреждение, непроверенное):
...
while (i < ARRAY_SIZE)
{
size_t j = 0;
int c;
while (j < sizeof buf - 1 && (c = getchar()) != EOF) && isdigit(c))
buf[j++] = c;
buf[j] = 0;
if (isdigit(c))
{
printf("Input too long to handle\n");
while ((c = getchar()) != EOF && c != '\n') // clear out input stream
/* empty loop */ ;
break;
}
else if (!isspace(c))
{
if (isgraph(c)
printf("Non-digit character %c seen in numeric input\n", c);
else
printf("Non-digit character %o seen in numeric input\n", c);
while ((c = getchar()) != EOF && c != '\n') // clear out input stream
/* empty loop */ ;
break;
}
else
a[i++] = (int) strtol(buffer, NULL, 10); // no need for follow pointer,
// since we've already checked
// for non-digit characters.
}
Добро пожаловать в чудесно взломанный мир интерактивного ввода в C.