1.scanf输入字符串解析
直接上代码,如下所示:
1 #include <stdio.h>
2 #include <string.h>
3 int main(int argc, char *argv[])
4 {
5 char str[100];
6 // gets(str);
7 scanf("%s",str);
8 printf("输入的字符串为:\n");
9 printf("%s\n",str);
10 //打印字符串最后一个数据(结束符\0前面一个)
11 printf("最后一个字符为:%c\n",str[strlen(str)-1]);
12 //输入结束打印后再次读取缓冲区数据
13 printf("请再次输入\n");
14 printf("读取打印缓冲区数据:%x\n",getchar());
15 return 0;
16 }
只需要输入1次即可得到代码的执行结果,如下图所示:
fly@fly-virtual-machine:~/test$ ./2
11111
输入的字符串为:
11111
最后一个字符为:1
请再次输入
读取打印缓冲区数据:a
fly@fly-virtual-machine:~/test$
从上述代码执行结果可以看出,打印最后一个字符‘1’之后,再次使用getchar获取输入缓冲区的内容得到的十六进制结果0x0a,其对应的ASCII表中的‘\r’(关于回车对应的ASCII是\r还是\n等结果请自行百度查看其区别)。从执行结果可以看出,scanf并未将输入缓冲区数据全部读取,当执行到getchar时,因为输入缓冲区存在数据,并不需要我们从键盘再次数据内容,程序即可执行结束。如果我们输入“111+空格+1”时会是怎么样呢?下图是程序执行结果:
111 1
输入的字符串为:
111
最后一个字符为:1
请再次输入
读取打印缓冲区数据:20
fly@fly-virtual-machine:~/test$
此时读取到str中的字符串为“111”,执行getchar获取的数据为space对应的ASCII码值,此时输入缓冲区后续还存在字符‘1’和回车,程序未进一步读取测试。若上次输入中的空格改成“111+TAB+1”,执行结果中str任然只有“111”,getchar获取输入缓冲区的数据应该为tab键对应的ASCII码值0x09,程序执行结果如下图所示:
fly@fly-virtual-machine:~/test$ ./2
111 1
输入的字符串为:
111
最后一个字符为:1
请再次输入
读取打印缓冲区数据:9
fly@fly-virtual-machine:~/test$
从上述三次测试结果可以看出,sacnf输入时遇到空格,tab或者回车时会终止输入,后续输入内容不会读取到变量中(包含输入结束符空格,tab或者回车),只会存在数据输入缓冲区中等待进一步读取,可以使用getchar,scanf或者gets函数进行获取。
gets输入字符串解析
将上述代码中的scanf输入更改为gets输入,代码如下所示:
1 #include <stdio.h>
2 #include <string.h>
3 int main(int argc, char *argv[])
4 {
5 char str[100];
6 gets(str);
7 // scanf("%s",str);
8 printf("输入的字符串为:\n");
9 printf("%s\n",str);
10 //打印字符串最后一个数据(结束符\0前面一个)
11 printf("最后一个字符为:%c\n",str[strlen(str)-1]);
12 //输入结束打印后再次读取缓冲区数据
13 printf("请再次输入\n");
14 printf("读取打印缓冲区数据:%x\n",getchar());
15 return 0;
16 }
执行结果如下图所示:
fly@fly-virtual-machine:~/test$ ./2
11111
最后一个字符为:1
请再次输入
代码运行时,第一次输入“11111”字符串之后,此时终端提示再次输入内容,后续输入‘2’之后执行结果如下图所示:
fly@fly-virtual-machine:~/test$ ./2
11111
最后一个字符为:1
请再次输入
2
读取打印缓冲区数据:32
fly@fly-virtual-machine:~/test$
分析上述代码可知,gets函数从输入缓冲区获取输入内容之后,输入缓冲区此时为空,即回车的内容被gets函数读取了,但是并未赋值到str中,当执行到getchar时,需要用户在终端再次从键盘上输入内容。下面展示分别输入“111+空格+1”和“111+TAB+1”的执行结果。
输入“111+空格+1”执行结果:
fly@fly-virtual-machine:~/test$ ./2
111 1
输入的字符串为:
111 1
最后一个字符为:1
请再次输入
2
读取打印缓冲区数据:32
fly@fly-virtual-machine:~/test$
输入“111+TAB+1”执行结果:
fly@fly-virtual-machine:~/test$ ./2
111 1
输入的字符串为:
111 1
最后一个字符为:1
请再次输入
2
读取打印缓冲区数据:32
fly@fly-virtual-machine:~/test$
分析上述三次测试,gets函数读取到回车结束符后,执行后续的打印函数;当执行到getchar时,此时输入数据缓冲区无内容,所以程序在等待我们从键盘上输入,输入之后程序才会彻底结束。得出如下结论:gets把空格和tab作为字符读取且并不会导致读取结束,遇到回车键时才会停止并且回车键并不会保存在数据缓冲区中。
小结
scanf和gets函数均可实现字符串的输入,但是scanf遇到空格 tab或者回车就会结束输入,结束符以及后续输入内容保存在输入数据缓冲区中;gets输入时,空格和tab键均作为字符读取且不会造成读取结束,gets执行结束后输入数据缓冲区中无输入结束符。但是gets函数使用上存在风险。gets如果没有遇到回车会持续读取数据,设计程序是应保证字符串长度足够大,防止数据溢出造成严重后果。
fgets
上述分析我们得出get存在隐患,因此我们引入新的函数fgets从指定的流中读取数据。Linux C中程序运行时会自动打开stdin,stdout和stderr三个流指针,其分别对应着标准输入,标准输入和标准错误;从终端输入我们则可以利用stdin标准输入流指针结合fgets实现。
char buffer[100] = {0};
fgets(buffer,sizeof(buffer),stdin);
if('\n' == buffer[strlen(buffer)-1])
buffer[strlen(buffer)-1] = '\0';
else
while('\n' != getchar());
此函数需要换行符,或者sizeof(buffer)-1个字符后会自动结束读取,若字符串最后一个字符为‘\n’,则因为终端输入结束(此时我们需要处理掉被读取到字符串中的‘\n’,这样才能和在终端上输入的字符串保持一致);否则因为我们设置读取固定长度导致的读取函数结束,此时我们需要处理掉缓冲器的数据,否则会影响下次我们的操作。代码如下所示:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char buff[10];
for(int i = 0;i < sizeof(buff);i++)
{
printf("%d\t",buff[i]);
}
printf("\n");
fgets(buff,sizeof(buff),stdin);
for(int i = 0;i < sizeof(buff);i++)
{
printf("%d\t",buff[i]);
}
printf("\n");
for(int i = 0;i < sizeof(buff);i++)
{
putchar(buff[i]);
putchar('\t');
}
putchar(10);
return 0;
}
运行结果(其中10为‘\n’的ASCII码值),可以看出会把‘\n’读取到字符串中切自动帮我们在尾部补上字符串结束标志‘\0’;最后一行乱码是因为程序未对buff进行初始化导致的。
fly@fly-virtual-machine:~/test$ ./1
0 0 48 -59 28 -24 -3 127 0 0
123
49 50 51 10 0 -24 -3 127 0 0
1 2 3
� �
继续上代码,入下图:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buff[10];
fgets(buff,sizeof(buff),stdin);
// if('\n' == buff[strlen(buff)-1])
// buff[strlen(buff)-1] = '\0';
printf("%s\n",buff);
return 0;
}
若注释部分未开启,在终端输入123回车后,会出现两个换行符,运行结果如下图所示,因此我们使用fgets进行字符串进行输入时需要处理尾部的‘\n’。
fly@fly-virtual-machine:~/test$ ./1
123
123
fly@fly-virtual-machine:~/test$
若fgets不是结束后buff中无‘\n’该如何进行处理呢?在处理的情况下继续调用fgets测试看下结果,代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buff[10];
fgets(buff,sizeof(buff),stdin);
if('\n' == buff[strlen(buff)-1])
buff[strlen(buff)-1] = '\0';
printf("%s\n",buff);
fgets(buff,sizeof(buff),stdin);
if('\n' == buff[strlen(buff)-1])
buff[strlen(buff)-1] = '\0';
printf("%s\n",buff);
return 0;
}
运行结果如下所示,因为第一个fgtes读取到123456789刚好9个字节,最后一个自动补‘\0’(上面说过最大为sizeof(buff)-1个字节);第一个fget执行完毕之后缓冲区还存在“00\n”三个字节,因此当再次执行到fgets时则自动读出且存在‘\n’会导致fgets自动结束;所以终端只需要输入一次“12345678900\n”即可执行结束,且第二次读取到“00”(因为‘\n’被我们使用程序处理成了‘\0’)。
fly@fly-virtual-machine:~/test$ ./1
12345678900
123456789
00
fly@fly-virtual-machine:~/test$
根据上述执行结果如果我们希望第一在终端输入不影响第二个fgets,我们需要将代码修改如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buff[10];
fgets(buff,sizeof(buff),stdin);
if('\n' == buff[strlen(buff)-1])
buff[strlen(buff)-1] = '\0';
//新增处理缓冲区剩余部分
else
while ('\n' != getchar());
printf("%s\n",buff);
fgets(buff,sizeof(buff),stdin);
if('\n' == buff[strlen(buff)-1])
buff[strlen(buff)-1] = '\0';
printf("%s\n",buff);
return 0;
}
使用getchar()循环读取缓冲区,直至遇到‘\n’,这时缓冲区数据已被我们全部 清除,则不会影响后续的fgets输入。
fly@fly-virtual-machine:~/test$ ./1
12345678900
123456789
注意事项
若在fgets前使用scanf进行输入,因为scanf会把‘\n’保留在缓冲区中,因此在使用fgets前需要我们清除下缓冲区,否则fgets会因为scanf输入保留在缓冲区的‘\n’直接读取结束。测试代码如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int number;
char buff[10];
scanf("%d",&number);
fgets(buff,sizeof(buff),stdin);
if('\n' == buff[strlen(buff)-1])
buff[strlen(buff)-1] = '\0';
else
while ('\n' != getchar());
printf("%s\n",buff);
return 0;
}
运行结果如下所示:
fly@fly-virtual-machine:~/test$ ./1
10
fly@fly-virtual-machine:~/test$