Creating a Friendlier User Interface

I l @ ve RuBoard

Creating a Friendlier User Interface

Most of us have on occasion written programs that are awkward to use. Fortunately, C gives you the tools to make input a smoother, more pleasant process. Unfortunately, learning these tools could, at first, lead to new problems. The goal in this section is to guide you through some of these problems to a friendlier user interface, one that eases data entry and smoothes over the effects of faulty input.

Working with Buffered Input

Buffered input is often a convenience to the user, but it can be bothersome to the programmer when character input is used. The problem, as you've seen in some earlier examples, is that buffered input requires you to press the Enter key to transmit your input. This act also transmits a newline character that the program must handle. Let's examine this and other problems with a guessing program. You pick a number, and the computer tries to guess it. The computer uses a plodding method, but we are concentrating on I/O, not algorithms. See Listing 8.5 for the starting version of the program.

Listing 8.5 The guess.c program.
 /* guess.c -- an inefficient number-guesser */ #include <stdio.h> int main (void) {   int guess = 1;   char response;   printf("Pick an integer from 1 to 100. I will try to guess ");   printf("it.\nRespond with a y if my guess is right and with");   printf("\nan n if it is wrong.\n");   printf("Uh...is your number %d?\n", guess);   while ((response = getchar()) != 'y')       /* get response */       printf("Well, then, is it %d?\n", ++guess);   printf("I knew I could do it!\n");   return 0; } 

Here's a sample run:

 Pick an integer from 1 to 100. I will try to guess it. Respond with a y if my guess is right and with an n if it is wrong. Uh...is your number 1?  n  Well, then, is it 2? Well, then, is it 3?  n  Well, then, is it 4? Well, then, is it 5?  y  I knew I could do it! 

Out of consideration for the program's pathetic guessing algorithm, we chose a small number. Note that the program makes two guesses every time you enter n . What's happening is that the program reads the n response as a denial that the number is 1 and then reads the newline character as a denial that the number is 2.

One solution is to use a while loop to discard the rest of the input line, including the newline character. This has the additional merit of treating responses such as no or no way the same as a simple n . The version in Listing 8.5 treats no as two responses. Here is a revised loop that fixes the problem:

 while ((response = getchar()) != 'y')    /* get response */ {     printf("Well, then, is it %d?\n", ++guess);     while (getchar() != '\n')        continue;           /* skip rest of input line    */ } 

Using this loop produces responses like the following:

 Pick an integer from 1 to 100. I will try to guess it. Respond with a y if my guess is right and with an n if it is wrong. Uh...is your number 1?  n  Well, then, is it 2?  no  Well, then, is it 3?  no sir  Well, then, is it 4?  forget it  Well, then, is it 5?  y  I knew I could do it! 

That takes care of the problems with the newline character. However, as a purist, you might not like f being treated as meaning the same as n . To eliminate that defect, you can use an if statement to screen out other responses. Change the loop to this:

 while ((response = getchar()) != 'y')     /* get response */ {    if (response == 'n')       printf("Well, then, is it %d?\n", ++guess);    else       printf("Sorry, I understand only y or n.\n");    while (getchar() != '\n')       continue;                 /* skip rest of input line */ } 

Now the program's response looks like this:

 Pick an integer from 1 to 100. I will try to guess it. Respond with a y if my guess is right and with an n if it is wrong. Uh...is your number 1?  n  Well, then, is it 2?  no  Well, then, is it 3?  no sir  Well, then, is it 4?  forget it  Sorry, I understand only y or n.  n  Well, then, is it 5?  y  I knew I could do it! 

When you write interactive programs, you should try to anticipate ways in which users might fail to follow instructions. Then you should design your program to handle user failures gracefully. Tell them when they are wrong, and give them another chance.

You should, of course, provide clear instructions to the user, but no matter how clear you make them, someone will always misinterpret them and then blame you for poor instructions.

Mixing Numeric and Character Input

Typically, you use getchar() to read input one character at a time and you use scanf() to read numbers or strings. This mixture can create problems because getchar() reads every character, including spaces, tabs, and newlines. In contrast, scanf() , when reading numbers, skips over spaces, tabs, and newlines. However, when reading characters using the %c specifier , scanf() behaves like getchar() .

To illustrate a representative problem, Listing 8.6 presents a program that reads in a character and two numbers as input. It then prints the character using the number of rows and columns specified in the input.

Listing 8.6 The showchar1.c program.
 /* showchar1.c -- program with an I/O problem */ #include <stdio.h> void display(char c, int lines, int width); int main(void) {      int ch;             /* character to be printed    */      int rows, cols;     /* number of rows and columns */      printf("Enter a character and two integers:\n");      while ((ch = getchar()) != EOF)      {        scanf("%d %d", &rows, &cols);        display(ch, rows, cols);        printf("Enter another character and two integers;\n");        printf("Simulate eof to quit.\n");      }      return 0; } void display(char c, int lines, int width) {      int row, col;      for (row = 1; row <= lines; row++)      {           for (col = 1; col <= width; col++)                putchar(c);           putchar('\n');   /* end line and start a new one */      } } 

Note: the program reads a character as type int to enable the EOF test. However, it passes the character as type char to the display() function. Because char is smaller than int , some compilers will warn about the conversion. In this case, you can ignore the warning.

The program is set up so that main() gets the data, and the display() function does the printing. Let's look at a sample run to see what the problem is.

 Enter a character and two integers:  c 2 3  ccc ccc Enter another character and two integers; Simulate eof to quit.  b 1 2  Enter another character and two integers; Simulate eof to quit.  bb  Enter another character and two integers; Simulate eof to quit.  ^Z  Enter another character and two integers; Simulate eof to quit.  ^Z  

The program starts off fine. You enter c 2 3 , and it prints two rows of three c characters, as expected. Then you enter b 1 2 , and it prints one row with two b characters. In between, it prints eight blank lines! What's wrong? It's that newline character again, this time the one immediately following the 3 on the first input line. Because getchar() doesn't skip over newline characters, this newline character is read by getchar() during the next cycle of the loop, becoming the character to be displayed.

Then scanf() has a turn at exposing our poor programming. Because getchar() reads the newline, the next input is the b character, so the scanf() function attempts to read it as an integer and fails. rows retains its previous value of 2 . (At least, that is how our system behaves on a failed match; ANSI C doesn't require any particular behavior in that instance.) Then scanf() tries to read the cols value. Input is still stuck on the b , so that read fails, too, and cols remains at 3 . The net result is that the program tells display() to print two rows of three newline characters. Each newline character generates a new line, giving a total of eight blank lines. Then the next loop cycle begins, and getchar() finally gets to read the b . This clears the way for scanf() to read the next two numbers. Similarly, the newline following this line forces you to enter Ctrl+Z twice to end the program. (We're assuming the program is run on a DOS system.)

To clear up this problem, the program has to skip over any newlines or spaces between the last number typed for one cycle of input and the character typed at the beginning of the next line. Also, it would be nice if the program could be terminated at the scanf() stage in addition to the getchar() test. The next version, shown in Listing 8.7, accomplishes this.

Listing 8.7 The showr2.c program.
 /* showchar2.c -- prints characters in rows and columns */ #include <stdio.h> #include <ctype.h> void display(char c, int lines, int width); int main(void) {    int ch;           /* character to be printed    */    int rows, cols;   /* number of rows and columns */    printf("Enter a character and two integers:\n");    while ((ch = getchar()) != EOF)    {       if (!isspace(ch))       {         if (scanf("%d %d",&rows, &cols) != 2)           break;         display(ch, rows, cols);         printf("Enter another character and two integers;\n");         printf("Simulate eof to quit.\n");       }    }    return 0; } void display(char c, int lines, int width) {      int row, col;      for (row = 1; row <= lines; row++)      {           for (col = 1; col <= width; col++)                putchar(c);           putchar('\n');   /* end line and start a new one */      } } 

The if statement causes the program to skip over the scanf() and display() statements if ch is whitespace. This means you can enter data fairly freely :

 Enter a character and two integers:  a 2 3  aaa aaa Enter another character and two integers; Simulate eof to quit.  b 1 2 c 3 5  bb Enter another character and two integers; Simulate eof to quit. ccccc ccccc ccccc Enter another character and two integers; Simulate eof to quit.  [Ctrl+Z]  

By using an if statement with a break , you terminate the program if the return value of scanf() is not 2 . This occurs if one or both input values are not integers or if end-of-file is encountered .

By using the if statement to skip spaces as well as newlines, the program can handle the second input line. If you omit the space skipping, the input line would have to look like this:

 b 1 2c 3 5 

Otherwise, a space between the 2 and c would be read into ch , and scanf() would try to read c as an integer.

I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net