Skip to content
Physics and Astronomy
Home Our Teaching Resources C programming unformatted.html
Back to top

Unformatted and binary input and output

Recap: formatted input

The scanf() family of functions we have used so far are intelligent functions for situations where we know what to expect (for example, an integer) and want interpret the input accordingly.

They are extremely convenient for reading in numbers as they skip over white space, including new lines. The user can leave any number of spaces between inputs, or even just put one per line. This is referred to as formatted input as the function has to know the format of the data it is expecting (integer, floating-point number, text string without spaces, etc.)

After it has read the expected characters, the system leaves itself positioned at the next character after the last one it has used, which is nearly always a space or a new-line, '\n'.

An example

Consider a file that starts:

1 2 
8.7
32.8
Mary had a little lamb
...

As far as our program is concerned, it is as if it were a giant character string starting:

Notice how I had accidentally typed a space at the end of the first line. This is quite common.
"1 2 \n8.7\n32.8\nMary had a little lamb\n..."

If the code now executes the statement:

fscanf(infile, "%d %d %lf", &j, &k, &x);

The value 1, 2 and 8.7 get read into j, k and x respectively with fscanf() conveniently skipping over unwanted spaces and new-line characters. Having read in everything up to and including "8.7" the imaginary character string now contains:

You will notice the remaining "string" starts with a newline character, '\n' . This will be very important later on.
"\n32.8\nMary had a little lamb\n..."

As long as we continue to use fscanf() to read in integer and numbers everything will be fine as all the unwanted spaces just get skipped over.

Unformatted input: reading in text without interpreting it

Quick discussion:

In this section we shall be dealing with situations where it is fairly easy to describe what we want to happen but actually making it happen involves some irritating and potentially confusing details.

Turn to your neighbour and ask:

  • In situations such as these, what should be our instinctive reaction?.

Sometimes we just want to read in a whole line of text into a character array, referred to as unformatted input. We don't expect it to have any special form such as a number. The most common reason is to be able to input text containing spaces, for example people's names or free-form text for a notebook application.

We shall first look at the mechanics of reading in the text and then deal with the subtleties of combining formatted and unformatted input.

Unformatted input: fgets()

The fgets() function reads a line from a file without interpreting it. That is, fgets() reads the unread input up to and including the next new-line character. The "file" can be the keyboard if stdin (standard input) is used. It has the form:

  fgets(buffer, maxbytes, file);
Here file is a FILE *. It can be obtained obtained in the usual way using fopen() or we could use the predefined value stdin if we want to read from standard input.

buffer is a character array at least maxbytes long. Like snprintf(), fgets() is "well behaved" and always puts a zero, '\0', at the end of the text even if the input line is too long, thus always leaving a valid zero-terminated string.

Notice that for unformatted input the FILE is the last argument, unlike in fscanf() where it is the first.

Thus fgets() reads in at most maxbytes - 1 bytes of actual input, or up to the next new-line character, whichever comes first. It returns NULL if the read failed completely, for example if we have reached the end of the input file.

Removing the new-line character

If space permits, fgets() includes the new-line character, sent when the user presses the "Return" or "Enter" key. This is always the final character of the string. If we don't want this character, we just need to replace it with '\0', thus shortening the string by one.

The following snippet calls fgets() to read a line from standard input and then checks to see if the last character of a string stored inside a character array is '\n'. If so it replaces it with '\0', thus shortening the string by one.

If this were a program we were writing for other people to use we would need to consider what to do if the final character were not a new line as it probably indicates that the line was too long for out buffer.

  if (fgets(line, N, stdin) != NULL) {
    int end = strlen(line) - 1;
    if (line[end] == '\n')
      line[end] = '\0';
  }

With this we can look at a very short program that uses fgets() to read a line of text from the keyboard and print it out again:

Mixing formatted and unformatted input

Whether we have a file of data or are reading from the keyboard, we are always free to mix formatted and unformatted input.

A common situation is to use formatted input to get options from a menu, or to read in data values, and to then need to read in a complete line of text.

The first attempt often looks like this:

// 
// Flawed attempt to read in an integer followed by some text.
//
int main() {
  char line[N];
  int value;

  printf("Please enter the integer value\n");
  scanf("%d", &value);

  printf("Now please type in the text string, spaces are allowed!\n");

  if (fgets(line, N, stdin) != NULL) {
    // Chop off final '\n';
    int end = strlen(line) - 1;
    if (line[end] == '\n')
      line[end] = '\0';
  }

  printf("The value is %d, the text is >%s<\n", value, line);
  return 0;
}
Step through this code


The "conversation" goes like this:

Please enter the integer value
12
Now please type in the text string, spaces are allowed!
The value is 12, the text is ><

The user is given no chance to type in a line of text, instead fgets() just seems to read a completely blank line. What's happening?

The answer is that, when we typed in "12" we actually typed three characters, '1', '2' and the carriage return, '\n'. As in the previous example, the system reads in the two characters '1' and '2' that form the integer 12 and leaves itself positioned at the very next character, which is the new line '\n'. Thus the "next line" is completely empty!

We can illustrate this by typing at the keyboard not just "12<return>" (three keystrokes) but "12<space>abc<return>" (seven keystrokes). The final line of output now looks like this:

The value is 12, the text is > abc<

Recap: scanf() is reading the two characters '1' and '2', inspecting the next character, seeing it is white-space, which ends the number and is therefore ignored and left for the next input function to deal with. If that next function is another call to scanf() that's fine as scanf() skips over white-space. But fgets() doesn't so it just sees the new-line character which it treats as being an empty line.

There are various bad solutions at this point (some people's first reaction is just to read in one more character in the hope that nobody will ever type 12<space><return>), but the most common situation is that we require the input line to be non-blank. In this case it's easy to write a loop that carries on reading a line from the file, or keyboard, until it finds a line that contains a non-space character.

If we are reading from stdin it looks like this:

int readoneline(char line[], int maxbytes) {
  while ( 1 ) {
    int i;

    if ( fgets(line, maxbytes, stdin) == NULL ) {
      return 0;  // Out of data
    }

    // We don't the new-line character so chop it
    i = strlen(line) - 1;
    if ( line[i] == '\n')
      line[i] = '\0';

    // Look for a non-blank character.
    for (i = 0; line[i]; ++i)
      if ( isspace(line[i]) == 0) {
        return 1;
      }
  }
}

Step through a complete example

This is quite a useful function.

                                                                                                                                                                                                                                                                       

Validate   Link-check © Copyright & disclaimer Privacy & cookies Share
Back to top