现在的位置: 首页 > 综合 > 正文

C语言字符串输入函数的比较与解析

2018年04月21日 ⁄ 综合 ⁄ 共 3345字 ⁄ 字号 评论关闭

       许多ACMer在做ACM题时经常会被一些字符串输入搞混,如果认为几个字符串输入函数的功能基本一样那就大错特错了。今天把C语言中的三个字符串输入函数(scanf,gets,fgets)深入分析一下,以至于不要让太多ACMer字符串输入的阴沟里翻船。(只讨论在命令行中的标准输入)


       首先讨论一下最常见的scanf输入字符串。众所周知,使用scanf("%s")输入字符串有一个显著特点就是字符串中不能有空格,回车,以及制表符等空白字符。因为scanf遇到空白字符就结束字符串输入。下面请看这段代码:

#include <stdio.h>
#include <string.h>
#define N 20

int main()
{
    char str[N];
    scanf("%s", str);
    printf("%s\n", str);
    return 0;
}

编译运行后当你输入“abc def”后会怎么样?程序会得出怎样的结果?下面是我的测试结果

这说明了什么?scanf确实无法读取带有空白字符的字符串。因此在需要读取带空白字符的字符串时使用scanf是一种不明智的选择。


       接下来我讲一个scanf函数一个十分重要的特性:在命令行中用scanf函数做标准输入,只要你是以换行符(也就是回车键)结束输入并且你不是用scanf读取单个字符,那么这个换行符就会被放到输入缓冲区中。如果你的下次输入是输入字符时或者使用gets等函数输入字符串时,你的下次输入就可能会被影响。接下来做个试验,请看这段代码:

#include <stdio.h>
#include <string.h>
#define N 20

int main()
{
    char str[N];
    char ch;
    scanf("%s", str);
    printf("%s\n", str);
    ch = getchar();
    printf("%d\n", ch);
    return 0;
}

编译运行之后我输入“abc”再按回车结束输入,下面是我的测试结果

我用字符变量ch接受后续字符输入再打印该字符的ASCII码值,该字符ASCII码值为10,正好是换行符的ASCII码。光是scanf输入字符串还不够,我们应该试试使用scanf函数输入别的比如整数浮点数之类的来试试,这个试验我就不再在这里一一写代码。我试验过,结果还是一样:输出ch的ASCII码值还是10。说明scanf函数做输入以换行符结束会留下这个换行符在输入缓冲区而不是把这个换行符清除掉。当然我想说在这一种情况下scanf后面就没有换行符了,请看这段代码

#include <stdio.h>
#include <string.h>
#define N 20

int main()
{
    char str[N];
    char ch;
    scanf("%s", str);
    printf("%s\n", str);
    scanf("%c", &ch);
    printf("%d\n", ch);
    ch = getchar();     //读取第二个scanf后面的残留字符,实际上没有
    printf("%d\n", ch);
    return 0;
}

运行后使用和上面一样的输入,结果还是和上面一样的输出。

但是不同的是,程序结束,而是再等待输入,因为第一个scanf输入后留下的换行符被第二个scanf读取了,而换行符之后并没有后续输入,因此getchar函数在等待输入中。说明:并不是scanf生成换行符放在输入缓冲区,而是用户输入的换行符没有被scanf吃掉因此残留在缓冲区而已。


       接下来gets函数了。当我们需要读取带有空白字符的字符串时基本上非gets函数莫属了,gets函数恰好是专门读取有空白字符的字符串的。实际上gets函数一直读到换行符才停止。试验代码如下:

#include <stdio.h>
#include <string.h>
#define N 20

int main()
{
    char str[N];
    char ch;
    gets(str);
    printf("%s\n", str);
    printf("%c\n", str[strlen(str)-1]);     //输出字符串str最后一个字符,非'\0'
    ch = getchar();
    printf("%d\n", ch);
    return 0;
}

当我输入一个字符串“I am a student”后按回车键结束输入得到的结果是这样的


等待输入的是ch字符,说明gets函数输入字符串并没有像scanf函数一样留下一个换行符在输入缓冲区中。那么难道这个换行符被读到字符串中了?不!因为输出字符串最后一个字符发现这个字符并不是换行符而是字母‘t',这正好是我输入字符串的最后一个字符。说明gets函数会把换行符吃掉但是却并没有放在字符串中,相当于一个会读非换行符的空白字符的scanf函数加一个getchar的功能。


       下面,我们来看一下scanf和gets交替使用来读取字符串的现象。试验代码如下:

#include <stdio.h>
#include <string.h>
#define N 20

int main()
{
    char str1[N];
    char str2[N];
    char ch;
    scanf("%s", str1);
    gets(str2);
    printf("str1=%s;\n", str1);
    printf("str2=%s;\n", str2);
    ch = getchar();
    printf("ch = %d\n", ch);
    return 0;
}

我打算先输入“student”后按下回车键再输入另一个字符串的,可是当我输入完“student”并按下回车后第二个字符串就已经输入完毕了,现象如下:

很明显等待输入的是getchar函数并且字符串str2是一个空串(也就是直接就是字符串结束符'\0'的字符串,长度为0)。我们不妨根据之前得到的结论解释一下这是为什么:scanf函数留下了换行符在输入缓冲区中并且被gets函数读入,因为gets函数遇到换行符才结束字符串的输入,而换行符之前的字符已经被scanf函数读走了,因此gets函数读到的就是一个空字符串了,并且gets函数会吃掉换行符且不会把它放进字符串中,因此目前的getchar函数还在等待输入的状态。事实上你不妨做个试验使用gets函数输入字符串的时候你只按一个回车键,那么你gets函数读到的就是空字符串。很明显的一个结论就是:scanf函数是不可能读到空字符串的,因为它不会读取空白字符,因此会不停的等待输入直到有非空白字符为止。


       下面我们讲第三个字符串输入函数fgets了,fgets本来是文件的输入函数,但是也可以用于标准输入,它比gets函数的优势就是它能限制字符串输入的长度,避免了缓冲区溢出,有许多网络攻击都属于缓冲区溢出攻击。换而言之就是说fgets函数比gets函数安全。我们依旧使用前面的试验代码,只是将gets换成fgets,代码如下:

#include <stdio.h>
#include <string.h>
#define N 20

int main()
{
    char str1[N];
    char str2[N];
    char ch;
    scanf("%s", str1);
    fgets(str2, sizeof(str2), stdin);
    printf("str1=%s;\n", str1);
    printf("str2=%s;\n", str2);
    ch = getchar();
    printf("ch = %d\n", ch);
    return 0;
}

还是一模一样的输入,当输入“student”按回车后fgets就已经完成了输入,这说明fgets和gets函数还是很像的,但是有一个地方不一样,看看我的结果:


很明显等待输入的还是getchar函数,但是字符串str2却和之前不一样了,应该说fgets函数把换行符加到字符串里面去了,str2并不是一个空字符串,而是一个只有一个换行符的字符串,如果你输出str2的长度你就会发现它的长度是1。fgets与gets在输入字符串中除了限制输入字符串的长度以外的主要区别就在于fgets会将换行符读取到字符串里面而不是像gets函数那样将换行符销毁。


       至此,这三个C语言的字符串输入函数的分析与比较就结束了,如果有人还发现了什么别的玄机、、、不要忘记分享哦、、

抱歉!评论已关闭.