Помещение чисел, разделенных пробелом в массив

Я хочу, чтобы пользователь вводил числа, разделенные пробелом, и затем сохранял каждое значение как элемент массива. В настоящее время у меня есть:

while ((c = getchar()) != '\n')
{
    if (c != ' ')
        arr[i++] = c - '0'; 
}

но, конечно, это сохраняет одну цифру на элемент.

Если пользователь должен был ввести:

10 567 92 3

Мне нужно было сохранить значение 10 в arr[0], а затем 567 в arr[1] и т.д.

Должен ли я использовать scanf вместо этого как-то?

Ответ 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.

Ответ 2

Небольшое изменение кода: только приращение i при чтении пробела:

while ((c = getchar()) != '\n')
{
    if (c != ' ')
        arr[i] = arr[i] * 10 + c - '0'; 
    else
        i++;
}

Конечно, лучше использовать scanf:

while (scanf("%d", &a[i++]) == 1);

если у вас достаточно места в массиве. Кроме того, будьте осторожны, чтобы while выше заканчивался на ;, все делается внутри условия цикла.

Собственно, каждое возвращаемое значение должно быть проверено.

Ответ 3

Это будет работать.

#include<stdio.h>
void main()
{
     int i=0,j,arr[100];
     while(scanf("%d",&arr[i])!=-1)
     {
          i++;
     }
     for(j=0;j<i;j++)
     {
          printf("%d\t",arr[j]);
     }
}