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
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:
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,
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:
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:
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:
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.