在最开始学习字符串时,我们最常用的输入字符串函数就是——gets();
这个函数将输入缓冲区中的数据存储到字符型数组中,以换行符结束,并丢弃末尾的换行符添加一个空字符。他解决了scanf("%s",str);以空白符为截至的漏洞。
但是这个函数有一个致命的弱点——不能对数组溢出做出相应的处理。
我们都知道,在我们定义数组时先要确定数组的大小,系统会分配一段没被使用的连续空间给这个数组。在这片区域外,我们不知道其他的空间是否被使用,这就带来了一个问题——如果数组越界时,会存在安全隐患,但是gets函数并没有处理这种越界问题的机制。
比如:
#include<stdio.h>
int main()
{
char ch[10];
gets(ch);
puts(ch);
return 0;
}
这是一个简单的输入输出代码,但是我们在一开始定义数组时只分配了10个存储空间,而我往输入缓存区中输入了超过10的数据,gets函数将换行符之前的数据全都存进了ch数组。
众所周知,ch实际就是ch数组的头指针,gets从ch的头地址依次按内存顺序存入数据,这本身并没有问题,但是我们在定义数组ch时只关心ch可不可以存入10个数据,这10个数据之外的地方有没有存数据我们是不知道的。
如果凑巧,没有存入数据,这并没有关系;但如果存入了,就会将原来的数据覆盖。这会导致很大的安全隐患。而gets函数并没有检查是否存在这种数组越界。
由此诞生了gets的替代品。
替代品1:fgets()
特点一:指定读入的字符个数
fgets函数并不像gets那样只能从标准中读取数据,他更常用在读取文件中的数据。
fgets()需要传入三个参数,依次是要读入的对象,读入字符的最大数量,要读入的文件。
如果是要在标准输入中读入,将第三个参数设置为stdin(在stdio.h)中。
示例如:
#include<stdio.h>
int main()
{
char ch[10];
fgets(ch,10,stdin);
fputs(ch,stdout); // 与fgets配套的输出函数
return 0;
}
输出结果显示,fgets函数只会将第二个参数指定大小的字符读入字符数组中(包括换行符)。如果输入不足10就输入换行符,则将字符包括换行符读入字符数组中。
特点二:不会将字符串末尾的换行符丢弃再添一个空字符
#include<stdio.h>
int main()
{
char ch[10];
fgets(ch,10,stdin);
fputs(ch,stdout);
return 0;
}
还是输入输出的程序,这次我们输入的字符数小于可读入的最大字符数。我们依次输入了 ‘a’,‘b’,‘c’,’\n’ ,fgets将这四个字符都读入到了数组ch中,与gets有区别的是,他没有将末尾的换行符丢弃并添加一个空字符 ‘\0’ ,而是直接将末尾的换行符保留下来了。
如果要将换行符换成空字符,需人为进行操作。
操作如下(将 ‘\n’ 换成 ‘\0’ ):
#include<stdio.h>
int main()
{
char ch[10];
fgets(ch,10,stdin);
// 将 '\n' 换成 '\0'
int i = 0;
while(ch[i] != '\n')
i++;
ch[i] = '\0';
fputs(ch,stdout);
return 0;
}
上述代码中我们将末尾的 ‘\n’ 换成了 ‘\0’ ,输出数组ch时,没有再输出一个换行符。
特点三:保留多余字符
通过之前的例子我们已经知道,fgets函数最多只会读入指定的字符数,那那些没有读入的字符会何去何从呢?
先看下面的例子:
#include<stdio.h>
int main()
{
char ch[10],sh[10],dh;
// 输入
fgets(ch,10,stdin);
fgets(sh,10,stdin);
dh = getchar();
// 输出
fputs(ch,stdout);
putchar('\n'); // 分割 ch 和 sh
fputs(sh,stdout);
putchar('\n'); // 分割 sh 和 dh
putchar(dh);
return 0;
}
我们先输入了一句话 “i will ba a niubide girl” 。头10个字符是 “i will ba”,fgets将他读入到数组ch中。紧接着的10个字符 “ a niubid” ,fgets将他读入到数组sh中,后面的 ‘e’ 读入到 dh 中。后面的字符 “ girl” 我相信他也没用被丢弃,而是还在输入缓存区中。由此我们看出,fgets并没有将没有读入的数据丢弃,他们依然在输入缓存区中等待着之后的数据的读取。
有时你想将之后的数据进行丢弃。如下操作:
#include<stdio.h>
int main()
{
char ch[10],sh[10];
fgets(ch,10,stdin);
// 丢弃数据
while(getchar() != '\n')
continue;
// 丢弃数据后再读入一个数据
fgets(sh,10,stdin);
// 输出两个数组
fputs(ch,stdout);
putchar('\n'); // 分割 ch 和 sh
fputs(sh,stdout);
return 0;
}
提醒:最后一行的换行是因为 sh 保留了换行符,再进行输出得来的。
替代品二:gets_s()
特点一:丢弃换行符而不是保存他
对于末尾换行符的处理,在不超过数组范围时 gets_s 丢弃他而不是保存他,这点和 gets 一样和 fgets 不一样。
特点二:将多余的字符丢弃
当我们向输入缓存区中输入多于数组的字符数时,gets_s会将多余的字符丢弃以达到避免存在安全隐患的目的,不论你是否需要后面的字符。
.
.
.
.
.
比较三个函数:
情况一:目标存储区装得下输入行
这时三种情况都差不太多,有区别的是 gets 和 gets_s 会丢弃 ‘\n’ 添加一个 ‘\0’ ,而 fgets会保留末尾的换行符。
情况二:目标存储区装不下输入行
gets 会将数据全都读入数组,从数组的头地址开始依次读入。若越界后有数据则会覆盖原始 数据,从而存在安全隐患。
fgets 只读入指定的数据大小,剩下的数据依然在输入缓冲区中。
gets_s 只读入数组最大可读大小,剩下的数据全部丢弃。
欢迎大家指正和补充。