在老师布置作业的要求写一些小程序时,肯定是要与用户进行交互,比如输入1继续,输入0退出。
而做一个交互界面肯定是需要一个while(1)循环,让用户在不慎输入错误的情况下能重新输入。第一次在写的时候通常这样干:
int Menu(void)
{
printf("是否继续(输入1继续,输入0退出)");
int choice;
scanf("%d",&choice);
if(choice==1)
{
/*......*/
return 1;
}
else
return -1;
}
这样写似乎是没有问题,至少输入2,或者3下面的程序是没有问题的
#include<stdio.h>
int Menu(void);
int main(void)
{
/*......*/
while(1)
{
int Check=Menu();
if(Check==1)
break;
else
printf("输入有误,请重新输入!\n");
}
/*......*/
}
int Menu(void)
{
printf("是否继续(输入1继续,输入0退出)");
int choice;
scanf("%d",&choice);
if(choice==1)
{
/*......*/
return 1;
}
else
return -1;
}
但是如果输入的是a3你会发现程序就崩溃了,不停地输出,循环无法结束。
因为scanf并未能成功读取a3,从a开始(包括a)的数据都被留在了缓冲区。
提到缓冲区这里就简单说下scanf:
它本身是一个函数。
函数原型为int scanf(const char *format, ...);
调用格式scanf("<格式化字符串>", <参量表>);
scanf("%s")是不会读取空格
scanf从缓冲区读取数据,我们从键盘输入的数据按enter后就会进入缓冲区。
返回值就是成功读取的个数,而老师上课时候根本没有强调这一点,只是告诉我们要按照格式输入,从来不详细展开如果输入的数据与格式不匹配会发生什么。
这也是我第一次上C语言课时,vs有如下警告我不是很明白:
若我们对代码进行改进,把scanf放在一个if判断句里面就可以消除这个警告。
#include<stdio.h>
int Menu(void);
int main(void)
{
/*......*/
while(1)
{
int Check=Menu();
if(Check==1)
break;
else
printf("输入有误,请重新输入!\n");
}
/*......*/
}
int Menu(void)
{
printf("是否继续(输入1继续,输入0退出)");
int choice;
if(scanf("%d",&choice)&&choice==1)
{
/*......*/
return 1;
}
else
return -1;
}
回到上面的问题:
输入的是a3。那么scanf开始依次读取,遇到a,与%d不匹配,结束录入,返回1
为什么要强调依次?因为你输入1a3的时候虽然也是非法输入,但scanf是从1开始读取,遇到1,格式匹配成功读取,寻找下个,发现是a,不匹配,结束录入。所以choice就被赋值为1,而当choice为1的时候,Menu返回1,循环可以退出,就这段而言是能正常进行(也就是有选项)。但如果输入的是2a3可么仍然会崩溃,因为choice被赋值为2,但我们的判断是if(&&choice==1)才返回1,因此Menu返回-1,主函数Check不为1,循环仍继续进行,这里缓冲区是有a3残留,因此scanf不给我们机会重新输入,从缓冲区读取,然后失败返回-1,循环继续......
那么到这里也就能明白为什么程序输入非数字会有问题。
那么怎么解决呢?
我们可以把所有的输入一律视为字符串的输入就可以了
下面是改进的程序:
#include<stdio.h>
#include<string.h>
int Menu(void);
int main(void)
{
/*......*/
while (1)
{
int Check = Menu();
if (Check == 1)
break;
else
printf("输入有误,请重新输入!\n");
}
/*......*/
}
int Menu(void)
{
printf("是否继续(输入1继续,输入0退出)");
char choice[200];
gets_s(choice, 200);
if (strcmp(choice,"1")==0)
{
/*......*/
return 1;
}
else
return -1;
}
数组的大小可以自己定,这里我写的200。
如果你觉得char choice[200]这种还是有一定缺陷,建议去了解下C++的string类。
gets因为可以无限读取,易发生溢出,因此C11标准中废除了这个函数。
这里用的是get_s代替,如你所见,加上了最大读取数量200,以保证数组不会越界。
到这里我们的问题也就解决的差不多了,但是还没完。
我是以字符形式读取,虽然说是一定程度上保证输入的健壮性,但如果函数里面需要我们输入的数字呢?比如说我输入个50,函数里面需要运用,但我是以字符串形式读取的那该怎么办呢?
最直接的想法那肯定是转化成整形
刚好C语言也实现了这个函数:atoi
在头文件stdlib.h里面
int atoi(const char* str)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int Menu(void);
int main(void)
{
/*......*/
while (1)
{
int Check = Menu();
if (Check == 1)
break;
else
printf("输入有误,请重新输入!\n");
}
/*......*/
}
int Menu(void)
{
printf("是否继续(输入1继续,输入0退出)");
char choice[200];
gets_s(choice, 200);
if (strcmp(choice,"1")==0)
{
int t=atoi(choice);
printf("%d",t);
/*......*/
return 1;
}
else
return -1;
}
这里直接运行下程序你可以看到t确实是被赋值为了1。
以上便是我个人在C语言学习的过程中遇到的一些问题给出自己的想法,若有不对请指出