Previous: 4.3 New Control Constructs
Up: 4 Processing Character Data
Next: 4.5 Menu Driven Programs
Previous Page: 4.3.3 The continue Statement
Next Page: 4.5 Menu Driven Programs

4.4 Mixing Character and Numeric Input

We have seen how numeric data can be read with scanf() and character data with either scanf() or getchar(). Some difficulties can arise, however, when both numeric and character input is done within the same program. Several common errors in reading data can be corrected easily if the programmer understands exactly how data is read. In this section, we discuss problems in reading data and how they can be resolved.

The first problem occurs when scanf() attempts to read numeric data but the user enters the data incorrectly. (While the discussion applies to reading any numeric data, we will use integer data for our examples). Consider an example of a simple program that reads and prints integers as shown in Figure 4.24.

In this program, scanf() reads an integer into the variable n (if possible) and returns a value which is compared with EOF. If scanf() has successfully read an integer, the value returned is the number of conversions performed, namely 1, and the loop is executed. Otherwise, the value returned is expected to be EOF and the loop is terminated. The the first part of the while condition is:

(scanf("%d", &n) != EOF)
This expression both reads an item and compares the returned value with EOF, eliminating separate statements for initialization and update. The second part of the while condition ensures that the loop is executed at most 4 times. (The reason for this will become clear soon). The loop body prints the value read and keeps a count of the number of times the loop is executed. The program works fine as long as the user enters integers correctly. Here is a sample session that shows the problem when the user makes a typing error:

The user typed 23r. These characters and the terminating newline go into the keyboard buffer, scanf() skips over any leading white space and reads characters that form an integer and converts them to the internal form for an integer. It stops reading when the first non-digit is encountered, in this case, the 'r'. It stores the integer value, 23, in n and returns the number of items read, i.e. 1. The first integer, 23, is read correctly and printed , followed by a prompt to type in the next integer.

At this point, the program does not wait for the user to enter data; instead the loop repeatedly prints 23 and the prompt but does not read anything. The reason is that the next character in the keyboard buffer is still 'r'. This is not a digit character so it does not belong in an integer; therefore, scanf() is unable to read an integer. Instead, scanf() simply returns the number of items read as 0 each time. Since scanf() is trying to read an integer, it can not read and discard the 'r'. No more reading of integers is possible as long as 'r' is the next character in the buffer. If the value of the constant EOF is -1 (not 0), an infinite loop results. (That is why we have included the test of cnt to terminate the loop after 4 iterations).

Let us see how we can make the program more tolerant of errors. One solution to this problem is to check the value returned by scanf() and make sure it is the expected value, i.e. 1 in our case. If it is not, break out of the loop. The while loop can be written as:

while ((flag = scanf("%d", &n)) != EOF) {
     if (flag != 1) break;
     printf("n = %d\n", n);
     printf("Type an integer, EOF to quit\n");
}
In the while expression, the inner parentheses are evaluated first. The value returned by scanf() is assigned to flag which is the value that is then compared to EOF. If the value of the expression is not EOF, the loop is executed; otherwise, the loop is terminated. In the loop, we check if a data item was read correctly, i.e. if flag is 1. If not, we break out of the loop. The inner parentheses in the while expression are important; the while expression without them would be:
(flag = scanf("%d", &n) != EOF)
Precedence of assignment operator is lower than that of the relational operator, ; so, the scanf() value is first compared with EOF and the result is True or False, i.e. 1 or 0. This value is then assigned to flag, NOT the value returned by scanf().

The trouble with the above solution is that the program is aborted for a simple typing error. The next solution is to flush the buffer of all characters up to and including the first newline. A simple loop will take care of this:

while ((flag = scanf("%d", &n)) != EOF) {
     if (flag != 1)
          while (getchar() != '\n');
     else {
          printf("n = %d\n", n);
          printf("Type an integer, EOF to quit\n");
     }
}
If the value returned by scanf() when reading an integer is not 1, then the inner while loop is executed where, as long as a newline is not read, the condition is True and the body is executed. In this case, the loop body is an empty statement, so the condition will be tested again thus reading the next character. The loop continues until a newline is read. This is called flushing the buffer.

The trouble with this approach is that the user may have typed other useful data on the same line which will be flushed. The best solution is to flush only one character and try again. If unsuccessful, repeat the process until an item is read successfully. Figure 4.25 shows the revised program that will discard only those characters that do not belong in a numeric data item.

Sample Session:

The input contains several characters that do not belong in numeric data. Each of these is discarded in turn and another attempt is made to read an integer. If unable to read an integer, another character is discarded. This continues until it is possible to read an integer or the end of file is reached.

Even if the user types data as requested, other problems can occur with scanf(). The second problem occurs when an attempt is made to read a character after reading a numeric data item. Figure 4.26 shows an example which reads an integer and then asks the user if he/she wishes to continue. If the user types 'y', the next integer is read; otherwise, the loop is terminated.

This program produces the following sample session:

The sample session shows that an integer input is read correctly and printed; the prompt to the user is then printed, but the program does not wait for the user to type the response. A newline is printed as the next character read, and the program terminates. The reason is that when the user types the integer followed by a RETURN, the digit characters followed by the terminating newline are placed in the keyboard buffer (we have shown the n explicitly). The function scanf() reads the integer until it reaches the newline character, but leaves the newline in the buffer. This newline character is then read as the next input character into c. Its value is printed and the loop is terminated since the character read is not 'y'.

A simple solution is to discard a single delimiting white space character after the numeric data is read. C provides a suppression conversion specifier that will read a data item of any type and discard it. Here are some examples:

scanf("%*c");            /* read and discard a character */
     scanf("%*d");            /* read and discard an integer */
     scanf("%d%*c", &n);      /* read an integer and store it in n, */
                              /* then read and discard a character */
     scanf("%*c%c", &ch);     /* read and discard a character, */
                              /* and read another, store it in ch, */
Figure 4.27 shows the revised program that discards one character after it reads an integer.

This program produces the following sample session:

  • ***Numeric and Character Data***
  • Type an integer
  • 23n
  • n = 23
  • Do you wish to continue? (Y/N): yn
  • debug:y in input stream
  • Type an integer
  • 34 n
  • n = 34
  • Do you wish to continue? (Y/N): debug: in input stream

We have shown the terminating newline explicitly in the sample session input. The first integer is read and printed; one character is discarded and the next one read correctly as 'y' and the loop repeats. The next integer is typed followed by some white space and then a newline. The character after the integer is a space which is discarded and the following character is read. The new character read is another space, and the program is terminated because it is not a 'y'.

The solution is to flush the entire line of white space until a newline is reached. Then the next character should be the correct response. The revised program is shown in Figure 4.28 and the sample session is below:

  • ***Numeric and Character Data***
  • Type an integer
  • 23 n
  • n = 23
  • Do you wish to continue? (Y/N): y n
  • debug:y in input stream
  • Type an integer
  • 34 n
  • n = 34
  • Do you wish to continue? (Y/N): n n
  • debug:n in input stream

The first integer is read and printed, the keyboard buffer is flushed of all white space until the newline is read, and the next character is read to decide whether to continue or terminate the loop. The next character input is also terminated with white space; however, the next item to be read is a number and all leading white space will be skipped.

A final alternative might be to terminate the program only when the user types an 'n'; accepting any other character as a 'y'. This would be a little more forgiving of user errors in responding to the program. One should also be prepared for mistyping of numeric data as discussed above. A programmer should anticipate as many problems as possible, and should assume that a user may not be knowledgeable about things such as EOF keystrokes, will be apt to make mistakes, and will be easily frustrated with rigid programs.



Previous: 4.3 New Control Constructs
Up: 4 Processing Character Data
Next: 4.5 Menu Driven Programs
Previous Page: 4.3.3 The continue Statement
Next Page: 4.5 Menu Driven Programs

tep@wiliki.eng.hawaii.edu
Wed Aug 17 08:29:11 HST 1994