1. 什么是输入缓冲区
输入缓冲区:这是一个我们键盘输入与编译器读取之间的一个缓冲区域,编译器并不是直接读取我们在键盘上输入的信息,而是从输入缓冲区中读取的信息。
我们使用scanf、getchar等输入函数时,并不是直接从键盘中去获取,而是由键盘打到输入缓冲区中,然后scanf,getchar等输入函数再从输入缓冲区里面去获取数据。
2. scanf(“%s”,str)从输入缓冲区中获取数据
我们在使用scanf函数获取一个字符串时,大多数时候都会遇到一个状况,这里我们举一个输入密码然后登录的例子来看一下。
注:这里补充一个知识点,strcmp()函数,它的头文件是<string.h>,我们比较字符串是否相等,是用它来比较,而不是用 == ,strcmp(str1,str2)括号里面是放两个需要比较的字符串,如果两个字符串相等,则返回0,如果第一个字符串大于第二个字符串,则返回一个大于0的值,如果第一个字符串小于第二个字符串,则返回一个小于0的值。
大致的逻辑是:我们设密码为123456,输入完密码以后,我们还需要确认密码,如果密码正确,则显示登录成功代码如下:
#include <stdio.h>
int main()
{
char password[20] = { 0 };
printf("Please input a password :>");
scanf("%s", password);//输入密码
char input = 0;
printf("Please confirm the password(Y/N) :>");
scanf("%c", &input);//确认或取消密码
if ('Y' == input)//确认密码
{
if (strcmp(password, "123456") == 0)//如果密码为123456,则显示登录成功
printf("Login succeeded\n");
else
printf("Login failed\n");//密码不为123456则显示登录失败
}
else//取消确认
printf("Cancel confirmation\n");
return 0;
}
这是大多数人一开始想出来的逻辑,结果怎么样呢:
可以看到,在我们输入完密码之后,还没等我们输入确认或取消(Y/N)就直接显示了登录失败。这个问题应该是怎么解决呢?
我们输入密码时,是把123456\n输入到输入缓冲区中(\n
是回车),然后由scanf去从输入缓冲区中去获取,但我们平常用来输入字符串的scanf(“%s”,str)不会读取回车符和空格符,所以我们第一个scanf("%s", password);
只读取了123456,这时候输入缓冲区里面还剩一个\n
,导致第二个scanf("%c", &input);
把\n读取了,所以就导致了我们还没有输入(Y/N)就直接显示登录失败。
那这里我们在scanf("%c", &input);
之前加上一个getchar() 将\n读取了以后,还会不会这样呢?
如图,这样做我们就显示登录成功了,流程就是:
我们先输入一串字符串,随后再用getchar()来清空输入缓冲区中的\n,然后再输入Y\n,就显示登录成功了。注:最后输入缓冲区中还存在一个\n,这是我们输入Y\n后留下的,因为对后续没有什么影响了,所以我们在这里就不用清理了
3. 清理缓冲区
当然,如果是在这个场景,我们这样处理是没问题的,但如果换一个场景的话,就不行了,假如我们输入密码时:123456 abc\n
输入成这个样子(后面会有一些乱七八糟的东西),那这样的话,我们加一个getcaht()也无济于事了(scanf(“%s”,password)只吸收空格前面的内容)。如果不处理掉输入缓冲不过去里面的内容的话,也会发生这样的效果:
注:这里可以这样分析
scanf(“%s”,password)读取了空格之前的内容,也就是123456,所以输入缓冲区中还剩 abcd\n
,然后我们后面的getchar()也只会读取一个字符,也就是将空格读取了,所以后面的scanf(“%c”,input)就把缓冲区中的a读取了,这样就导致了我们上面出现的错误。
所以,我们应该要先清空输入缓冲区:
while (getchar() != '\n')
{
;
}
我们可以这样来处理掉缓冲区里面的杂物。
这里while判断的条件是getchar != ‘\n’,getchar读取到哪个字符,它的返回值就是哪个字符,所以,这里我们这样来清空缓冲区,是连带’\n’一起清理掉的(当getchar()吸收了’\n’,则getchar() == ‘\n’ ,不满足条件就退出while循环了)
这样我们就可以把输入缓冲区里面的杂物全都清理掉了。
最终代码写成这样:
#include <stdio.h>
#include <string.h>
int main()
{
char password[20] = { 0 };
printf("Please input a password :>");
scanf("%s", password);//输入密码
char input = 0;
//清空输入缓冲区
while (getchar() != '\n')
{
;
}
printf("Please confirm the password(Y/N) :>");
scanf("%c", &input);//确认或取消密码
if ('Y' == input)//确认密码
{
if (strcmp(password, "123456") == 0)//如果密码为123456,则显示登录成功
printf("Login succeeded\n");
else
printf("Login failed\n");//密码不为123456则显示登录失败
}
else//取消确认
printf("Cancel confirmation\n");
return 0;
}
这样的话不管我们怎么输入都能够很好的预测scanf()是这样读取数据的了。
注:这里密码依旧是123456,后面的 abcdefg
只是我们举的一个场景,来更方便我们理解输入缓冲区,具体分析上面有说到。
4.scanf函数的知识拓展
之前我们说过,scanf(“%s”,str),只能读取空格或者回车符前面的数据,那如果我们想要用scanf读取这些数据应该怎么办呢?
scanf("%[^\n]")
,这里^
表示“非”的意思,[^\n]
表示读取换行符之前的所有数据(包括空格,注:其中换行符还留在输入缓冲区),[^#]
就是读取#
前面的所有数据(包括空格,换行符,注:其中#还留在输入缓冲区),我们可以测试一下:
#include <stdio.h>
int main()
{
char str1[20] = { 0 };
char str2[20] = { 0 };
char str3[20] = { 0 };
scanf("%[^\n]", str1);//这里读取hello world
scanf("%[^#]", str2);//这里读取了\nhello China\n
scanf("%s", str3);//这里读取了#
printf("%s\n", str1);
printf("%s\n", str2);
printf("%s\n", str3);
return 0;
}
输入:
1.Hello world\n
2.Hello China\n#\n
3.Hello
输出:
1.Hello world
2.\nHello China\n
3.#
注:
'\n'
就是回车。这里我们在#之后加了一个回车键,所以我们输入的第三条语句(Hello)和#
后面的\n
还停留在输入缓冲区中
scanf("%*[^\n]%*c)
,这里*
表示该输入项读入后不赋予任何变量,即scanf("%*[^\n]%*c)
可以用来表示跳过一行字符串(前面的%*[^\n]
可以将\n
之前的数据全部读取但不赋予任何变量,后面的%*c
可以将回车读取,但不赋予任何变量),如:
#include <stdio.h>
int main()
{
char str1[20] = { 0 };
scanf("%*[^\n]%*c");
scanf("%[^\n]", str1);
printf("%s",str1);
return 0;
}
输入:
1.hello world\n
2.hello China\n
输出:
hello China
注:我们第一行输入的数据都被
scanf("%*[^\n]%*c");
所读取掉 了,此时输入缓冲区只有一个\n,是来自我们输入的第二行最后输入的’\n’