C Primer Plus(6) 中文版 第8章 字符输入/输出和输入验证 8.7 菜单浏览 8.8 关键概念 8.9 本章小结

发布于:2023-01-13 ⋅ 阅读:(387) ⋅ 点赞:(0)

8.7 菜单浏览
许多计算机程序都把菜单作为用户界面的一部分。菜单给用户提供方便的同时,却给程序员带来了一些麻烦。
菜单给用户提供了一份响应程序的选项。
理想状态是,用户输入程序所列选项之一,然后程序根据用户所选项完成任务。作为一名程序员,自然希望通过这一过程能顺利进行。因此,第1个目标是:当用户遵照指令时程序顺利运行;第2个目标是:当用户没有遵照指令时,程序也能顺利运行。显而易见,要实现第2个目标难度较大,因为很难预料用户在使用程序时的所有错误情况。
现在的应用程序通常使用图形界面,可以点击按钮、查看对话框、触摸图标,而不是我们示例中的命令行模式。但是,两者的处理过程大致相同:给用户提供选项、检查并执行用户的响应、保护程序不受误操作的影响。除了界面不同,它们底层的程序结构也几乎相同。
但是,使用图形界面更容易通过限制选项控制输入。
8.7.1 任务
菜单选项需要执行那些任务。它要获取用户的响应,根据响应选择要执行的动作。另外,程序应该提供返回菜单的选项。C的switch语句是根据选项决定行为的好工具,用户的每个选择都可以对应一个特定的case标签。使用while语句可以实现重复访问菜单的功能。因此,
我们写出以下伪代码:
获取选项
当选项不是'q'时
    转至相应的选项并执行
    获取下一个选项
8.7.2 使执行更顺利
当你决定实现这个程序时,就要开始考虑如何让程序顺利运行(顺利运行指的是,处理正确输入的错误输入时都能顺利运行)。这表明输入过程提供一个只返回正确响应的函数。结合while循环和switch语句,其程序结构如下:
#include <stdio.h>
void count( void );
int main( void ){
    int choice;
    
    while( (choice = get_choice()) != 'q' ){
        switch( choice ){
            case 'a': printf( "Buy low, sell high.\n" );
                break;
            case 'b': putchar( '\a' ); /*ANSI*/
                break;
            case 'c': count(); 
                break;
            default: printf( "Program error!\n" );
                break; 
        }
    }
    return 0;

定义get_choice()函数只能返回'a'、'b'、'c'和'q'。get_choice()的用法和getchar()相同,两个函数都是获取一个值,并与终止值作比较。default语句可以方便调试。如果get_choice()函数没能把返回值限制为菜单指定的几个选项值,default语句有助于发现问题所在。
get_choice()函数
下面的伪代码是设计这个函数的一种方案:
显示选项
获取用户的响应
当响应不合适时
    提示用户再次输入
    获取用户的响应
下面是一个简单而笨拙的实现:
char get_choice( void ){
    int ch;
    printf( "Enter the letter of your choice:\n" );
    printf( "a.advice         b.bell\n" );
    printf( "c.count          d.quit\n" );
    ch = getchar();
    while( (ch < 'a' || ch > 'c') && ch != 'q'){
        printf( "Please respond with a, b, c, or q.\n" );
        ch = getchar();
    }
    return ch;

缓冲输入依旧带来些麻烦,程序把用户每次按下Return键产生的换行符视为错误响应。为了让程序的界面更顺畅,该函数应该跳过这些换行符。
这类问题有多种解决方案。一种是用名为get_first()的新函数替换getchar()函数,读取一行的第1个字符并丢弃剩余的字符。这种方法的优点是,把类似act这样的输入视为简单的a,而不是继续把act中的c作为选项c的一个有效的响应。重写函数如下:
char get_choice( void ){
    int ch;
    printf( "Enter the letter of your choice:\n" );
    printf( "a.advice         b.bell\n" );
    printf( "c.count          d.quit\n" );
    ch = get_first();
    while( (ch < 'a' || ch > 'c') && ch != 'q'){
        printf( "Please respond with a, b, c, or q.\n" );
        ch = get_first();
    }
    return ch;

char get_first( void ){
    int ch;
    ch  = getchar(); /*读取下一个字符*/
    while( getchar() != '\n' )
        continue; /*跳过该行剩下的内容*/
    return ch; 
}
8.7.3 混合字符和数值输入
假设count()函数(选择c)的代码如下:
void count( void ){
    int n, i;
    printf( "Count how far? Enter an integer:\n" );
    scanf( "%d", &n );
    for( i = 1; i <= n; i++ ){
        printf( "%d\n", i );
    }

如果输入3作为响应,scanf()会读取3并把换行符留在输入队列中。下次调用get_choice()将导致get_first()返回这个换行符,从而导致我们不希望出现的行为。
一种方法是重写get_first(),使其返回下一个非空白字符而不仅仅是下一个字符,即可修复这个问题。这个任务楼给读者作为练习。另一种方法是,在count()函数中清理换行符,如下所示:
void count( void ){
    int n, i;
    printf( "Count how far? Enter an integer:\n" );
    n = get_int();
    for( i = 1; i <= n; i++ ){
        printf( "%d\n", i );
    }
    while( getchar() != '\n' ){
        continue;
    }

重写的get_first()函数如下:
char get_first( void ){
    while( getchar() != '\n' )
        continue;
    int ch;
    ch  = getchar(); /*读取下一个字符*/
    while( getchar() != '\n' )
        continue; /*跳过该行剩下的内容*/
    return ch; 

get_int()函数借鉴了get_long()函数,将其改为get_int()获取int类型的类型而不是long类型的数据。
/* menuette.c -- menu techniques */
#include <stdio.h>
char get_choice(void);
char get_first(void);
int get_int(void);
void count(void);
int main(void)
{
    int choice;
    void count(void);
    
    while ( (choice = get_choice()) != 'q')
    {
        switch (choice)
        {
            case 'a' :  printf("Buy low, sell high.\n");
                break;
            case 'b' :  putchar('\a');  /* ANSI */
                break;
            case 'c' :  count();
                break;
            default  :  printf("Program error!\n");
                break;
        }
    }
    printf("Bye.\n");
    
    return 0;
}

void count(void)
{
    int n,i;
    
    printf("Count how far? Enter an integer:\n");
    n = get_int();
    for (i = 1; i <= n; i++)
        printf("%d\n", i);
    while ( getchar() != '\n')
        continue;
}

char get_choice(void)
{
    int ch;
    
    printf("Enter the letter of your choice:\n");
    printf("a. advice           b. bell\n");
    printf("c. count            q. quit\n");
    ch = get_first();
    while (  (ch < 'a' || ch > 'c') && ch != 'q')
    {
        printf("Please respond with a, b, c, or q.\n");
        ch = get_first();
    }
    
    return ch;
}

char get_first(void)
{
    int ch;
    
    ch = getchar();
    while (getchar() != '\n')
        continue;
    
    return ch;
}

int get_int(void)
{
    int input;
    char ch;
    
    while (scanf("%d", &input) != 1)
    {
        while ((ch = getchar()) != '\n')
            putchar(ch);  // dispose of bad input
        printf(" is not an integer.\nPlease enter an ");
        printf("integer value, such as 25, -178, or 3: ");
    }
    
    return input;

/* 输出:

*/

在开发了一种可行的方案后,可以在其他情况下复用这个菜单界面。
注意在处理复杂的任务时,如果让函数把任务委派给另一个函数。这样让程序更模块化。 
8.8 关键概念
C程序把输入作为传入的字节流。getchar()函数把每个字符解释成一个字符编码。scanf()函数以同样的方式看待输入,但是根据转换说明,它可以把字符输入转换成数值。
在输入验证部分处理这些错误情况,让程序更强健更友好。
处理输入验证有多种方案。例如,如果用户输入错误类型的信息,可以在终止程序,也可以给用户提供有限次或无限次机会重新输入。
8.9 本章小结
编写程序时,要认真设计用户界面。实现预料一些用户可能会犯的错误,然后设计程序妥善处理这些错误情况。 


网站公告

今日签到

点亮在社区的每一天
去签到