下面是一段实现两个数加减乘除的简易计算器代码,由此发现了一些以前没有注意到的问题.


 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     double firstNum, secondNum;
 8     double result = 0;
 9     int operatoring;
10 
11     char buff1[10];
12     char buff2[10];
13 
14     printf("please input the first number:\n");
15     firstNum = atof(gets(buff1));
16 
17     printf("please input the operator: +, -, *, /\n");
18     operatoring = getchar();
19 //    while((operatoring = getchar()) != '\n' && operatoring != EOF);
20 //    fflush(stdin);
21 //    printf("%d\n", operatoring);
22 
23     printf("please input the second number:\n");
24     secondNum = atof(gets(buff2));
25 
26     switch (operatoring)
27     {
28         case 0x2B :
29             result = firstNum + secondNum;
30             break;
31         case 0x2D :
32             result = firstNum - secondNum;
33             break;
34         case 0x2A :
35             result = firstNum * secondNum;
36             break;
37         case 0x2F :
38             result = firstNum / secondNum;
39             break;
40     }
41     printf("result = %f\n", result);
42 
43     return 0;
44 }

char* gets(char* str) ------ 从标准输入(stdin)读入字符串并存入str,直到遇到换行符或者文件结束符(EOF),返回 str.
int getchar (void) ------ 从标准输入(stdin)读入一个字符,直到遇到换行符或者结束符(EOF),返回字符对应的ASCII值,由宏实现:#define getchar() getc(stdin).

运行结果:

问题1:
当程序运行到18行输入一个 + 再按Enter之后, 再运行到23行提示输入第二个数之后,窗口并没有等待输入,直接运行到42行打印结果.
也就是说 getchar()之后没有等待输入.

解释:
对于 getchar(),如果是用Enter结束输入时,Enter 键值也会被存入标准输入缓冲中,当此程序23行执行时直接读到Enter,结束,所以才会出现不等待的现象.

解法1:
既然是因为标准输入缓冲中还有残留的数据,那我用fflush(stdin)清空标准输入缓冲区就是了,于是修改上面的代码如下:
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     double firstNum, secondNum;
 8     double result = 0;
 9     int operatoring;
10 
11     char buff1[10];
12     char buff2[10];
13 
14     printf("please input the first number:\n");
15     firstNum = atof(gets(buff1));
16 
17     printf("please input the operator: +, -, *, /\n");
18     operatoring = getchar();
19     fflush(stdin);
20 
21     printf("please input the second number:\n");
22     secondNum = atof(gets(buff2));
23 
24     switch (operatoring)
25     {
26         case 0x2B :
27             result = firstNum + secondNum;
28             break;
29         case 0x2D :
30             result = firstNum - secondNum;
31             break;
32         case 0x2A :
33             result = firstNum * secondNum;
34             break;
35         case 0x2F :
36             result = firstNum / secondNum;
37             break;
38     }
39     printf("result = %f\n", result);
40 
41     return 0;
42 }
还是用Enter键结束getchar()的输入.
此代码在Windows平台(Code::Block 12.11)下运行正常,但是在Ubuntu14.04(gcc 4.8.2)下问题仍然存在,只能说fflush(stdin)在gcc下没有生效了.

PS. 关于 fflush(stdin)
C99 标准规定fflush(stdin)操作是未定义的,也就是说不一定能实现刷新功能,有些编译器作为一个扩展功能可能实现了,即依赖于具体实现,最好不要使用,移植性也不好.
也可参考C++定义 http://www.cplusplus.com/reference/cstdio/fflush/?kw=fflush 描述,原型为 int fflush(FILE *stream); 如果stream 已经被打开并指向输出流,或者是更新流(且最后一次操作是输出操作),则输出buffer中的任何待写数据将被写入到文件中,
In all other cases, the behavior depends on the specific library implementation. In some implementations, flushing a stream open for reading causes its input buffer to be cleared (but this is not portable expected behavior).

解法2:
使用 while((operatoring = getchar()) != '\n' && operatoring != EOF) 读完标准输入中的所有数据,于是代码如下:
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     double firstNum, secondNum;
 8     double result = 0;
 9     int operatoring;
10 
11     char buff1[10];
12     char buff2[10];
13 
14     printf("please input the first number:\n");
15     firstNum = atof(gets(buff1));
16 
17     printf("please input the operator: +, -, *, /\n");
18     operatoring = getchar();
19     while((operatoring = getchar()) != '\n' && operatoring != EOF);
20     printf("the last input = %d\n", operatoring);
21 
22     printf("please input the second number:\n");
23     secondNum = atof(gets(buff2));
24 
25     switch (operatoring)
26     {
27         case 0x2B :
28             result = firstNum + secondNum;
29             break;
30         case 0x2D :
31             result = firstNum - secondNum;
32             break;
33         case 0x2A :
34             result = firstNum * secondNum;
35             break;
36         case 0x2F :
37             result = firstNum / secondNum;
38             break;
39     }
40     printf("result = %f\n", result);
41 
42     return 0;
43 }
代码20行打印出getchar()返回值 = 10(下面第二张图),正好是换行符\n对应的ASCII值,验证了Enter键值确实也被读入了输入缓冲区.
但是实际的功能没有实现,运行结果如下:(还是用Enter键结束getchar()的输入)

所以正确是代码如下:( 注:此操作应该置于引入不必要的缓存getchar()之后,当前输入操作secondNum = atof(gets(buff2))之前 ).
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 void Stdin_Clean(void)
 6 {
 7     int c;
 8     do 
 9     {
10         c = getchar();
11     } while (c != '\n' && c != EOF);
12 }
13 
14 int main(int argc, char* argv[])
15 {
16     double firstNum, secondNum;
17     double result = 0;
18     int operatoring;
19 
20     char buff1[10];
21     char buff2[10];
22 
23     printf("please input the first number:\n");
24     firstNum = atof(gets(buff1));
25 
26     printf("please input the operator: +, -, *, /\n");
27     operatoring = getchar();
28     printf("the last input = %d\n", operatoring);
29 
30     printf("please input the second number:\n");
31     Stdin_Clean();
32     secondNum = atof(gets(buff2));
33 
34     switch (operatoring)
35     {
36         case 0x2B :
37             result = firstNum + secondNum;
38             break;
39         case 0x2D :
40             result = firstNum - secondNum;
41             break;
42         case 0x2A :
43             result = firstNum * secondNum;
44             break;
45         case 0x2F :
46             result = firstNum / secondNum;
47             break;
48     }
49     printf("result = %f\n", result);
50 
51     return 0;
52 }

运行结果:

解法3:
既然Enter键会被读入输入缓冲区,那也可以使用文件结束符(EOF)来结束getchar()的输入了,代码为:
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     double firstNum, secondNum;
 8     double result = 0;
 9     int operatoring;
10 
11     char buff1[10];
12     char buff2[10];
13     
14     printf("please input the first number:\n");
15     firstNum = atof(gets(buff1));
16 
17     printf("please input the operator: +, -, *, /\n");
18     operatoring = getchar();// use Ctrl + D instead of Enter to end the input 
19 
20     printf("please input the second number:\n");
21     secondNum = atof(gets(buff2));
22         
23     switch (operatoring)
24     {
25         case 0x2B :
26             result = firstNum + secondNum;
27             break;
28         case 0x2D :
29             result = firstNum - secondNum;
30             break;
31         case 0x2A :
32             result = firstNum * secondNum;
33             break;
34         case 0x2F :
35             result = firstNum / secondNum;
36             break;
37     
38     } 
39 
40     printf("result = %f\n", result);
41 
42     return 0;
43 }
当输入运算符'+'之后直接Ctrl+D结束输入,运行结果如下:

对于EOF的使用可参考 http://www.ahlinux.com/c/22765.html.
多数人认为文件中有一个EOF,用于表示文件的结尾. 但这个观点实际上是错误的,在文件所包含的数据中,并没有什么文件结束符,对getc 而言,如果不能从文件中读取,则返回一个整数-1,这就是所谓的EOF. 返回 EOF 无非是出现了两种情况,一是文件已经读完;二是文件读取出错,反正是读不下去了. 文件结束符EOF,Windows下为组合键 Ctrl+Z,Unix/Linux下为组合键Ctrl+D.
1.EOF作为文件结束符时的情况:
EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符.
(1)遇到getcahr函数执行时,输入第一个字符时就直接输入Ctrl+D,就可以跳出getchar(),去执行程序的其他部分;
(2)在前面输入的字符为换行符时,接着输入Ctrl+D;
(3)在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,第一次的Ctrl+D使getchar开始读取键盘缓冲区中的数据。
其实,这三种情况都可以总结为只有在getchar()提示新的一次输入时,直接输入Ctrl+D才相当于文件结束符.
2.EOF作为行结束符时的情况,这时候输入Ctrl+D并不能结束getchar(),而只能引发getchar()提示下一轮的输入.
这种情况主要是在进行getchar()新的一行输入时,当输入了若干字符(不能包含换行符)之后,直接输入Ctrl+D,此时的Ctrl+D 并不是文件结束符,而只是相当于换行符的功能,即结束当前的输入.

最后,代码中使用 gets(buff1) 从标准输入中读取字符串是不安全的,没有对buff[]的宽度检查而很容易导致越界访问(gcc 编译时也会提示 warning),所以最好是用fgets(buff1, 10, stdin),第二个参数10表示最大读取10个字符到buff1中,包括换行符. 确切地说,调用fgets函数时,最多只能读入n-1个字符. 读入结束后,系统将自动在buff1最后加'\0'. 参考 http://www.cplusplus.com/reference/cstdio/fgets/.

最终的代码:
1. 使用 Ctrl+D 结束输入
 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 
 5 int main(int argc, char* argv[])
 6 {
 7     double firstNum, secondNum;
 8     double result = 0;
 9     int operatoring;
10 
11     char buff1[10];
12     char buff2[10];
13     
14     printf("please input the first number:\n");
15     firstNum = atof(fgets(buff1, 10, stdin));
16 
17     printf("please input the operator: +, -, *, /\n");
18     operatoring = getchar();// use Ctrl + D instead of Enter to end the input 
19 
20     printf("please input the second number:\n");
21     secondNum = atof(fgets(buff2, 10, stdin));
22         
23     switch (operatoring)
24     {
25         case 0x2B :
26             result = firstNum + secondNum;
27             break;
28         case 0x2D :
29             result = firstNum - secondNum;
30             break;
31         case 0x2A :
32             result = firstNum * secondNum;
33             break;
34         case 0x2F :
35             result = firstNum / secondNum;
36             break;
37     
38     } 
39 
40     printf("result = %f\n", result);
41 
42     return 0;
43 }

 

2. 使用 Enter 结束输入
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void Stdin_Clean(void)
{
    int c;
    do 
    {
        c = getchar();
    } while (c != '\n' && c != EOF);
}

int main(int argc, char* argv[])
{
    double firstNum, secondNum;
    double result = 0;
    int operatoring;

    char buff1[10];
    char buff2[10];
    
    printf("please input the first number:\n");
    firstNum = atof(fgets(buff1, 10, stdin));

    printf("please input the operator: +, -, *, /\n");
    operatoring = getchar(); 

    printf("please input the second number:\n");
    Stdin_Clean();
    secondNum = atof(fgets(buff2, 10, stdin));

    switch (operatoring)
    {
        case 0x2B :
            result = firstNum + secondNum;
            break;
        case 0x2D :
            result = firstNum - secondNum;
            break;
        case 0x2A :
            result = firstNum * secondNum;
            break;
        case 0x2F :
            result = firstNum / secondNum;
            break;
    } 

    printf("result = %f\n", result);

    return 0;
}