周立功

控制台菜单选项的实现(2)——入口检查

0
阅读(3594)

    声明:在这一个系列中本着一题多解,步步深入以及尽量降低初学者的阅读难度减少枝叶代码的精神,很多细节都没有处理。例如举的例子中的函数的返回值都是void,函数中间没有安全检查,及其它的一些问题,通过例子中的几十行代码不可能做的面面俱到,在实际编程中是不能这样的,请同学们不要盲目效仿。

    在V0.1版本中,我们提到了输入参数的合法性检查问题,不过还是先介绍另一个非常重要的理念——“健壮性”。在大多数本科教材中也只是提到了这个概念,却并没有深入的去探讨,其实不然,对于一个合格的程序员来说,软件的健壮性理念必须放在首位。很多到企业实习的学生编写的代码,一般都能够正确地实现程序的功能,一些学生甚至还会做到功能上的扩展,但是所有的学生都有一个共同的毛病,从来不做异常处理,只要用户输入不合法,则程序很容易崩溃,这是不允许发生的,也是用户绝对不能容忍的。虽然软件在发布前必须经过大量的测试,但希望初学者必须养成良好的编程习惯。
 
    下面开始讨论V0.1版本软件接受用户输入的合法性判断。为什么要重点判断这个问题呢?道理很简单:“用户都是傻瓜”,当然,我们不是说使用者智商有任何问题,而是开发人员应该这么去想。开发者无法预知用户会输入那些奇奇怪怪的内容,但是好的程序都必须能够处理那些看来莫名其妙的问题。我们到底将如何解决这样的问题呢?
1.方法1
    “scanf("%d",&iCmdNum);”语句是V0.1版本出现死循环的关键,那么我们不妨先从这里入手。其实只要将“%d“改为“%c”,即可解决死循环问题。比如:
    scanf("%c", &chCmd);                                        // chCmd是一个字符变量
    iCmdNum = atoi(&chCmd);                                                                                      // 将chCmd转换为整型数值
    为什么这样做就能够解决死循环问题呢?我们不妨编写不同的测试用例(在这里仅仅需要输入不同的数值即可)试试。如果出现问题,将如何解释?
       ☛ 提示:
l         控制台中的所有输入都被认为是字符;
l         回车键是一个或两个字符(不同的操作系统和编译器有不同的解释,而在Windows控制台中回车键的ASCII码为10);
l         查看atoi的使用方法,注意它执行失败后的返回值。
    为什么将“%d“改为“%c”即可避免死循环?大家知道V0.1版本出现死循环的原因完全是因为scanf从缓冲区读取值失败所引起的。因为在输入输出流中所有的类型都会被当作字符来看待,那么当使用%c来读入chCmd时,不管输入什么样的值都会成功。当scanf成功之后,则会将缓冲区中的字符清空 (实际上是将“流指针”后移),等待下一次用户的输入,所以它不会出现死循环。
2.方法2
       既然问题是由用户输入函数带来的,那么我们即可使用其它标准库函数来解决这个问题。根据控制台菜单程序用户输入的特点,我们不妨输入单字符来触发功能,即通过getchar函数来实现用户交互。但是,我们一定要注意其中的细节,该函数的原型
    int getchar(void)
    在常见的c编译器里面,char类型取值范围为[-128, 127],类似EOF等宏的值则在此取值范围之外,那么EOF将不会被正确地读入,那么
       char c= getchar();
则是一个隐藏很深的bug。在这里我们将接受输入值的变量定义为int。
                      程序清单2   用if else做分支处理并用字符作比较(V0.2)
    void CmdRunning()
    {
        int iCmdNum=0;
       
        do {
             printf("请选择:0. 退出;1. 新建文件;2. 打开文件;3. 保存文件\n");
             iCmdNum = getchar();
             fflush(stdin);                                              // 清空缓冲区
             if ('0' == iCmdNum) {
                  printf("谢谢使用,再见!\n");
             }
             else if ('1' == iCmdNum) {
                   CreateFile();
             }
             else if ('2' == iCmdNum) {
                   OpenFile();
             }
             else if ('3' == iCmdNum) {
                   SaveFile();
             }
             else {
                   printf("对不起,你选择的数字不存在,请重新选择!\n");
             }
        }while('0' != iCmdNum);
     }
 
           下文预告:
     由于If else方式在处理多分支时显繁琐并且效率低效,它必须一个一个分支地判断。而处理多分支比较容易想到的就是switch 语句了。为了避免出现V0.1版本中的死循环,我们后面的例子将使用另外一个输入函数。
 
                                                                       2010.9.27 于杭州