一、scanf
scanf比起常规意义上的字符串输入函数,更确切地说它其实是单词输入函数。由于scanf函数规定输入从第一个非空字符开始,直到遇到空白(空格、回车或制表符)停止输入,所以实质上它只能一次性输入一个中间没有空格的单词。
如:
char a[10];
scanf("%s", a);
输入 good morning, 则实质储存入数组的只有good,而其余字符“ morning\n”则会继续留在缓冲区(buffer)内,如果下次的字符串输入函数依然是scanf,则函数会跳过morning之前的那个空格,由第一个非空字符开始。事实上无论morning之前有多少空白,scanf都会直接跳过。
如果输入行太长,有可能会擦去现有数据,详情可以了解一下缓冲区。而为了防止这钟安全隐患出现,可以使用
scanf("%10s", a);
来控制字符串输入读取长度,这样scanf函数就只会读取10个字符,避免危险。但总体来说scanf对于普通的字符串输入依然是不太方便的。
二、gets
gets函数的参数只需要数组名/指针名,并且不像scanf那样需要担心空白影响输入。而且这个函数还贴心地帮你读取输入末尾的'\n'并删除后换成'\0',好像是好用不少。
但是我们还是会面临着输入行太长,擦去现有数据的风险。而gets函数本身没有有效的防范手段。C11标准委员会直接从标准中废除了gets函数。而既然标准已经发布,那么编译器就必须根据标准来调整支持什么,不支持什么。然而在实际应用当中,编译器为了能兼容更早期的代码,大部分都继续支持gets函数。
但一般情况下,gets还是较为方便的。
那有没有更好的函数呢?
三、gets_s
C11新增gets_s函数为其可选扩展,此函数的第二个参数为限制读入的字符数。若第二个参数为n,则最终可以最多有n-1个字符被读入。
假如我们要读入9个字符,可以这样写:
gets_s(words, 10);//words为已定义的某长度>=10的数组
类似于gets函数,gets_s函数遇到换行符也不会储存它,而是丢弃他。
可是如果gets_s函数督导最大字符数都没有读到换行符,会执行下面几步。首先把目标数组中的首字符设置为空字符‘\0’,读取并丢弃随后的输入直至换行符或文件结尾,然后返回空指针。接着,调用依赖实现的”处理函数“,可能会中止或退出程序。
但事实你并不一定知道怎么编写特殊的”处理函数“,所以这也就是为什么gets_s函数只是作为标准的可选拓展。
四、fgets
fgets函数通过第二个参数限制读入的字符数来解决溢出问题。该函数专门设计用于处理文件输入,所以一般情况下可能不太好用。fgets和gets的区别如下:
1. fgets函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n,那么fgets将读入n-1个字符,或者遇到的第一个换行符为止。这点与gets_s函数类似,但fgets没有令人头疼的中止程序问题。
2. 如果fgets读到一个换行符,会把它存储在字符串中。这点与gets不同,gets会丢弃换行符。
3. fgets函数的第三个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin(标准输入)作为参数,该标识符定义在stdio.h中。
不足处1. 考虑下面这个情况
fgets(words, 14, stdin);
puts(words);
输入:apple pie(回车)
则输出:apple pie(换行)+(换行)
为什么会有两次换行?第一个是因为fgets函数将换行符也储存到了数组之中。第二个则是因为puts函数末尾会自带的换行符。又是一个不方便的点。
不足处2. 如果fgets的输入一次超出了第二个参数所限制的数量,那么余下的字符将会留在缓冲区中,进一步对我们造成影响。
所以我们考虑,把读入的'\n'直接换为'\0',而将超出限制部分直接删去。
于是就有了一个相对较为优秀的自建函数。
五、s_gets
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if(ret_val)//防止读取到文件结尾或读取失败,返回空指针null
{
while(st[i] != '\n' && st[i] != '\0')
i++;
if(st[i] == '\n')//替换换行符
st[i] = '\0';
else//删去多余输入
while(getchar() != '\n')
continue;
}
return ret_val;
}
当然这也只是较为初步的一种优化。事实上删去多余输入可能在许多其他的情况下给人带来麻烦,这就需要继续的学习,改进了。