Putting numbers separated by a space into an array

There are several approaches, depending on how robust you want the code to be.

The most straightforward is to use scanf with the %d conversion specifier:

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

The %d conversion specifier tells scanf to skip over any leading whitespace and read up to the next non-digit character. The return value is the number of successful conversions and assignments. Since we're reading a single integer value, the return value should be 1 on success.

As written, this has a number of pitfalls. First, suppose your user enters more numbers than your array is sized to hold; if you're lucky you'll get an access violation immediately. If you're not, you'll wind up clobbering something important that will cause problems later (buffer overflows are a common malware exploit).

So you at least want to add code to make sure you don't go past the end of your array:

while (i < ARRAY_SIZE && scanf("%d", &a[i++]) == 1)
  /* empty loop */;

Good so far. But now suppose your user fatfingers a non-numeric character in their input, like 12 3r5 67. As written, the loop will assign 12 to a[0], 3 to a[1], then it will see the r in the input stream, return 0 and exit without saving anything to a[2]. Here's where a subtle bug creeps in -- even though nothing gets assigned to a[2], the expression i++ still gets evaluated, so you'll think you assigned something to a[2] even though it contains a garbage value. So you might want to hold off on incrementing i until you know you had a successful read:

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

Ideally, you'd like to reject 3r5 altogether. We can read the character immediately following the number and make sure it's whitespace; if it's not, we reject the input:

#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;
  }
}

If we get two successful conversions, we make sure follow is a whitespace character - if it isn't, we print an error and exit the loop. If we get 1 successful conversion, that means there were no characters following the input number (meaning we hit EOF after the numeric input).

Alternately, we can read each input value as text and use strtol to do the conversion, which also allows you to catch the same kind of problem (my preferred method):

#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;
  }
}

BUT WAIT THERE'S MORE!

Suppose your user is one of those twisted QA types who likes to throw obnoxious input at your code "just to see what happens" and enters a number like 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 which is obviously too large to fit into any of the standard integer types. Believe it or not, scanf("%d", &val) will not yak on this, and will wind up storing something to val, but again it's an input you'd probably like to reject outright.

If you only allow one value per line, this becomes relatively easy to guard against; fgets will store a newline character in the target buffer if there's room, so if we don't see a newline character in the input buffer then the user typed something that's longer than we're prepared to handle:

#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;
  }
  ...
}

If you want to allow multiple values per line such as '10 20 30', then this gets a bit harder. We could go back to reading individual characters from the input, and doing a sanity check on each (warning, untested):

...
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.
}

Welcome to the wonderfully whacked-up world of interactive input in C.

Tags:

C