《Back C语言常见问题目录
目录
一、引例
1.这是一个很简单的例子,读入一个整数和一个字符,并将它们打印出来
#include <stdio.h> int main(){ char ch; int num=0; printf("请输入一个整数: \n"); scanf("%d",&num); printf("请输入一个字符: "); getchar();//过滤前面scanf的空格或回车 scanf("%c",&ch);//与ch=getchar();等价 printf("num=%d,ch=%c",num,ch); return 0; }
可以看到上述例子中在第二次调用scanf读入字符时,在scanf前面多了一个getchar(),用以过滤上一个scanf遗留的回车。
如果我们把getchar()注释掉:
2.现在我们把问题简化一下,只输入一个字符并打印
#include <stdio.h> int main(){ char ch; printf("请输入一个字符: \n"); //getchar(); scanf("%c",&ch);//与ch=getchar();等价 printf("ch=%c",ch); return 0; }
在getchar()语句被注释的情况下,可以看到程序正确地读到了字符
如果getchar()未被注释:
什么时候用getchar()过滤空格或回车?这里getchar()的作用和原理是什么?
要想真正弄明白这些问题,必须深入理解C语言的缓冲区的概念。
二、原理
1.概念
在内存中预留指定大小的存储空间,用来对输入或输出的数据做临时存储,这部分预留的内存空间叫缓冲区。C语言读入/输出数据,并不是直接读入/输出的,而是先把这些数据暂存到缓冲区中,再从缓冲区中读入或打印到屏幕上(如图)。
由于输入缓冲区与输出缓冲区相互独立,所以一般情况下C语言的输出函数不会影响到输入函数的执行。输出函数相对而言是不容易出错的,我们在写代码时应重点考虑scanf、getchar、gets等输入函数怎么写,才能取到我们想要的值。
2.缓存区的刷新
程序运行时,系统会默认为标准输入/输出分配一个缓冲区,这个缓冲区的大小在头文件stdio.h中定义,通常为512字节。
下面几种情况发生时缓冲区会刷新(清空):缓冲区满时;使用fflush函数刷新缓冲区;使用endl语句;关闭文件。
缓冲区还有其他类型,这里主要针对的是C语言标准输入输出的缓冲区。
3.回过头来分析前面的程序
#include <stdio.h> int main(){ char ch; int num=0; printf("请输入一个整数: \n"); scanf("%d",&num); printf("请输入一个字符: "); getchar();//过滤前面scanf的空格或回车 scanf("%c",&ch);//与ch=getchar();等价 printf("num=%d,ch=%c",num,ch); return 0; }
其输入缓冲区变化如下:
执行 scanf("%d",&num);
执行 getchar();//过滤前面scanf的空格或回车
执行 scanf("%c",&ch);//与ch=getchar();等价
解析:C语言标准输入输出函数采用行缓冲。scanf("%d",&num)语句执行后,程序读入一个整数以及回车符,缓冲区还剩下\n。此时需要getchar()函数过滤前面缓冲区中的回车,清空缓存区,显示屏才会闪烁光标提示,允许用户用键盘继续输入。这时scanf("%c",&ch)语句赋给ch的值才是用户输入的字符。而scanf语句结束后缓存区依然剩下了\n。
4.总结
在程序开始运行时,系统默认为标准输入/输出函数分配了输入输出缓冲区(需要程序包含stdio.h),这时缓冲区是空的。
第一次调用输入函数时,显示屏光标闪烁,提示用户输入。用户可以输入很长的一串字符,直至敲下回车时输入结束。这一串字符(包括回车符)会保存到缓冲区中,根据调用的输入函数以及其参数,从缓冲区中读取适当长度的一串字符给程序,同时缓冲区清除这一串送给程序的字符,这时缓冲区中仍可能剩余一些字符(比如回车符等)。
再次调用输入函数时,若缓冲区不为空,则将缓冲区中的字符返回给程序,缓冲区清除相应字符;若缓冲区为空,则显示屏光标闪烁,提示用户输入。在用户键入的一串字符包括回车符保存到缓冲区后,由调用的输入函数将指定格式的一串字符返回程序,同时缓冲区清除相应字符。
打个比方,如果说用户在键盘上敲入的字符流是水流,键盘是注水口,而输入函数是控制水流的水龙头,那么缓冲区就是能暂时存储水的水箱。我们设想的情况是用户往注水口里注入水流,这些水会暂时保存在水箱里。在水龙头打开的时候,水流从水箱流向水龙头。
当我们打开水龙头(调用输入函数)时,有两种情况:1.如果水箱里没水(缓冲区为空),(程序)就会请求用户往注水口里注水(键盘输入字符)。注水完成后,注入的水并不会直接从水龙头出来,而是先流入水箱(缓冲区)。由水龙头阀门打开的大小(被调用的输入函数的格式)决定水龙头的水流量,水龙头流出多少体积的水,那水箱就相应减少多少体积的水。当关闭水龙头时,水箱中仍可能有剩余一些水。2.如果水箱里有剩余的水,那么水龙头优先获取水箱里暂存的水,这时(程序)不会请求用户往注水口里注水(键盘输入字符)。
总的来说,关于输入函数要把握三个要点:1.键盘输入的值先暂存到输入缓冲区中;2.键盘输入结束的回车符会一起保存到缓冲区中;3.一般在输入函数结束后,缓冲区仍有剩余字符(很有可能是回车符)。
三、解决办法
1.最简单的办法
根据输入情况和输入函数调用情况,分析缓冲区,在恰当的时候用getchar()过滤多余的字符。
2.用fflush清除缓冲区(不推荐!)
在输入函数后面使用fflush(stdin);语句可以清空缓冲区。
但是fflush(stdin)有一个严重的问题!!!
那就是C和C++的标准里从来没有定义过fflush(stdin)。fflush 是对C标准的扩充,某些编译器(如VC6)支持用 fflush(stdin)来清空输入缓冲,但是并非所有编译器都支持这个功能,比如gcc3.2就不支持。
3.手动清除缓冲区(推荐写法)
#include <stdio.h>
int main(){
char ch;
int num=0;
printf("请输入一个整数: \n");
/*清除缓冲区缓冲*/
if (scanf("%d", &num)!=EOF){
while ((ch=getchar())!='\n' && ch!=EOF);
}
/*清除缓冲区缓冲end*/
printf("请输入一个字符: \n");
scanf("%c",&ch);
printf("num=%d,ch=%c",num,ch);
return 0;
}
四、拓展(附标准输入类型格式符)
1.下面举了一个例子,帮助你更好地理解输入缓冲区
#include <stdio.h> int main(){ char ch,ch1,ch2; printf("请输入一个字符: \n"); ch=getchar(); ch1=getchar(); ch2=getchar(); printf("|ch=%c,ch1=%c,ch2=%c|",ch,ch1,ch2); return 0; }
//输入asdfg[回车]
//输入a[回车]s[回车]
解析:输入函数返回多长的数据,缓冲区就相应缩短多少,直至缓冲区为空时刷新,此时(缓冲区为空时)若还有输入函数待执行,光标会闪烁等待用户输入
2.一个特殊的例子,scanf读入字符格式为"\n%c"或" %c"
#include <stdio.h> int main(){ char ch; int num=0; printf("请输入一个整数: \n"); scanf("%d",&num); printf("请输入一个字符: \n"); scanf("\n%c",&ch);//scanf(" %c",&ch); printf("|num=%d,ch=%c|",num,ch); return 0; }
3. 输入类型格式符
1)有符号类型
int(%d)、short(%hd)、long(%ld)、long long(%lld)、char(%c、%d、%u)、
float(%f)、double(%lf)、long double(%Lf)
2)无符号类型
unsigned char(%c、%d、%u)、unsigned short(%hu、%ho、%hx)、unsigned int(%u,、%o、%x)、unsigned long(%lu、%lo、%lx)、unsigned long long(%llu、%llo、%llx),
一些编译器支持不在C标准中的类型,例如VC++6.0支持64位整数_int64(%I64d)
- scanf读入多个数据举例(类型格式符之间无空格):
int v1=0,v2=0; float v3=0; scanf("%d%d%f",&v1,&v2,&v3);//多变量读入 printf("v1=%d,v2:%d,v3为%f\n",v1,v2,v3);//输出
- double类型变量输入输出:
double x=0; scanf("%lf",&x);//读入double类型变量,格式符为%lf printf("%.2f",x);//输出double类型变量,格式符为%f,使用.n控制精度