C语言 关于字符串的输入

MOOC上这一节MOOC:关于字符串的存储与表示的例题,大致代码如下:

#include <stdio.h>
#define N 5

int main()
{
    char b[N + 1];
    char *a = b;
    printf("Input:");
    gets(a);
    printf("%s\n",a);
    return 0;
}

但是在VS2017的实际运行中,出现了一些问题。

关于gets()函数

首先是gets处给出的提示:
gets处的提示
这是由于C11中,用更安全的gets_s()代替了gets(),原因就是gets()不能检查输入行的长度,从而带来由于输入的字符串太长而带来的隐患。
这里不得不提一下scanf_s()函数,scanf()也是由于在读取时不检查边界,所以可能会造成内存访问越界。而这两个函数和其他一些函数(如sprintf_s())的后缀_s都是缓冲区的大小,表示最多读取n-1个字符,避免引用到不存在的元素。

gets_s()与字符指针

将函数修改成gets_s()后,仍然有问题:
修改成gets_s()后
这又是为什么呢?

因为Visual Studio用的是老的C标准,即C89,gets函数(包括Visual Studio中的gets_s)要求函数参数必须是char数组名,不能是char指针;现在的dev C++用的是C99标准,gets函数的参数允许使用char指针。

因此,本例题最关键的通过指针输入数组的功能由于VS的C99泡了汤。

关于数组越界

当我们修改程序,删去指针相关内容,代码简化成这样:

#include <stdio.h>
#define N 5

int main()
{
    char b[N + 1];
    printf("Input:");
    gets_s(b);
    printf("%s\n",b);
    return 0;
}

注意到数组b的长度为N+1,原因是字符串都以空字符NULL结尾,所以字符串b中只能有5个字符,当输入6个字符后,产生以下结果:
错误提示

Debug Assertion Failed! Program: D:\练习\Project3\Debug\Project3.exe
File: minkernel\crts\ucrt\src\appct\stdio\gets.cpp Line: 103
Expression: (L”Buffer is too small” && 0) For information on how your
program can cause an assertion failure,see the Visual C+ +
documentation on asserts. (Press Retry to debug the application)

缓冲区太小,也就是缓冲区的长度被设置成5,不能输入太长的字符串。

fgets()函数

实际上,C语言中一直有的fgets函数就可以替换不安全的gets()函数。函数定义如下:

从文件结构体指针stream中读取数据,每次读取一行。读取的数据保存在buf指向的字符数组中,每次最多读取bufsize-1个字符(第bufsize个字符赋’\0’),如果文件中的该行,不足bufsize个字符,则读完该行就结束。如若该行(包括最后一个换行符)的字符数超过bufsize-1,则fgets只返回一个不完整的行,但是,缓冲区总是以NULL字符结尾,对fgets的下一次调用会继续读该行。函数成功将返回buf,失败或读到文件结尾返回NULL。因此我们不能直接通过fgets的返回值来判断函数是否是出错而终止的,应该借助feof函数或者ferror函数来判断。

fgets函数原型为char *fgets(char *buf, int bufsize, FILE *stream),其中*buf是字符型指针,指向用来存储所得数据的地址;bufsize是整型数据,指明存储数据的大小;*stream是文件结构体指针,即将要读取的文件流。用键盘输入的话,stream参数改为stdin,我们将代码改为下面的:

#include <stdio.h>
#define N 5

int main()
{
    char b[N + 1];
    char *a = b;
    printf("Input:");
    fgets(a,N+1,stdin);
    printf("%s\n",a);
    return 0;
}

这里*buf既可以是char数组名,也可以是char指针。这时候我输入六个字符并回车,结果是这样的:

我们看到程序成功运行,只是输出了前五个字符,也就是说缓冲区中的U没有被读入。
因此,fgets()函数的第二个参数限制了读取范围,更加安全。

换行符的读取

我们仍然使用gets_s(),输入四个字符,结果是这样的:

而使用fgets(),结果是这样的:

对比发现使用fgets()输入的输出除了printf的换行符外还有一个换行符,就是输入四个换行符之后的回车符。这是由于gets_s()输入的时候并不读取换行符’\n’,而是把换行符替换成空字符’\0’,作为C语言字符串结束的标志,而fgets()函数是会读取换行符的
因此,使用fgets()输入五个字符,结果:

这个时候fgets()输入的字符串其实已经达到了上限,即N,回车仅仅代表输入结束,不会被fgets读取了。

猜你喜欢

转载自blog.csdn.net/twentyonepilots/article/details/79223598