目录
引言
字符串在大多数编程语言中都有着举足轻重的地位,在C语言中,字符串亦是如此.
本篇博客将简单介绍一下缓存区的概念和几个跟字符串输入输出有关的函数.
一、缓冲区
概念
系统为了加快程序运行的速度预留出来的、暂时置放输出或输入资料的一块内存空间
为什么需要缓冲区
程序运行时,在CPU中控制单元控制下,计算机从磁盘中读取对应的数据。但是与内存相比,磁盘的读写速度是很慢的,如果我们每次获取数据都从磁盘中读取,程序运行效率将大打折扣,所以我们需要有一块内存中的区域(因为内存的读写速度较快)能暂时置放输出或输入资料,也就是缓冲区,它的主要作用是解放CPU,举一个简单的例子,离小明家3公里的地方有个超市,小明每次去购物的时候都要走很远的路,但是现在小明买了一个储物柜放在家里来临时的放那些东西,这样小明每次只要从超市多拿一些东西,就会变得很方便,这个储物柜的作用就相当于缓冲区。
还有一点原因是:磁盘文件的存取单位是“块”,一般为512字节,而在程序中给变量或数组元素赋值却是一个一个进行的,如果每次需要一些数据时计算机都读取一个“块”大小的数据,那么岂不是很不方便?
二、字符串的输入
三者的共同特点
在stdin(标准输入缓存区)中读取数据,如果stdin中已有数据则直接读取,否则等待用户输入后数据缓存完成,stdin采用行缓冲的方式(即当输入输出过程遇到换行符’’\n"或者当分配缓冲区已满时,才开始执行 I\O 操作。一般涉及终端的读写操作如 stdin 与 stdout 使用这种缓冲方式),因此键入回车键时刷新缓冲区,这一点我们结合下面的应用来理解。
gets()函数
在stdin中读取数据直到读到换行符,丢弃换行符。
#include<stdio.h>
int main()
{
char str[100];
gets(str);
char ch=getchar();
printf("str is s\nch is %c\n",str,ch);
return 0;
}
输入:1234
运行结果:
str is 1234
ch is �
gets()函数只有一个地址参数,所以用起来比较方便,但是正是因为只有一个地址参数,用户的输入就变得不可控了,当数组足够容纳用户输入的字符和那个’\0’时,gets()函数是简单好用的,但是当数组不够大的时候,gets()函数会继续往这片空间后面的内存空间写数据,导致缓存区溢出,如果这片被写的内存空间放着很重要的东西,程序就会崩溃。
正是因为如此,制定C99的委员会建议不要使用它,C11甚至强硬地废除了它。
在gcc编译器中它会给出这样的警告:
warning: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
gets();
^~~~
fgets
/tmp/ccQSL0sA.o:在函数‘main’中:
boke.c:(.text+0x1d): 警告: the `gets' function is dangerous and should not be used.
在浙大OJ平台PTA上,gets()也被认为是一个不安全的函数。
scanf()函数
scanf()函数不会读取stdin中的换行符,将换行符留在stdin中。
和gets()函数一样,它也无法检查数组能否装的下输入行,如果输入行的内容过长,它也会导致数据溢出,但是它可以在转换说明中使用字段宽度来防止溢出。
例如:
#include<stdio.h>
int main()
{
char str1[100],str2[100];
scanf("%2s %6s",str1,str2);
printf("str1 is %s\nstr2 is %s");
}
输入:abcdefg回车
运行结果:
str1 is ab
str2 is cdefg
scanf()函数的具体用法可以参考一下:C语言scanf函数用法完全攻略
fgets()函数
百度百科:
fgets函数功能为从指定的流中读取数据,每次读取一行。其原型为:char *fgets(char *str, int n, FILE *stream);从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
值得一提的是,fgets()函数可能会把键入的回车键读取并存储在字符串中。如果用户输入的字符串超过指定的最大长度,则不添加换行符,但是’\0’显然必须加上,所以最后一个位置留给’\0’;如果未超过指定的最大长度,那么换行符和’\0’都会留在数组中。
#include<stdio.h>
int main()
{
char str1[5],str2[5];
fgets(str1,5,stdin);
fgets(str2,5,stdin);
printf("str1 is %s\nstr2 is %s\n",str1,str2);
printf("*****");
}
输入:1234567回车
运行结果:
str1 is 1234
str2 is 567
*****
上述两个数组的存储内容如下:
str1 | 1 | 2 | 3 | 4 | \0 |
---|---|---|---|---|---|
str2 | 5 | 6 | 7 | \n | \0 |
利用这个特性,当我们没有检测到换行符时,我们就知道用户输入的数据较大了。
gets_s()函数
gets_s()函数也能指定字符串输出的最大长度,但是它只从stdin中读取数据,因此相比fgets()函数,它没有第三个参数,并且当它读到换行符时会丢弃它而不是储存它。
因为当gets_s()函数读到最大字符数时都没有读到换行符时会做一些复杂的事情,可能会中止或退出程序,C11只规定它为可选函数,因此笔者也不对它进行具体描述。
C Primer Plus第四版中自定义的s_gets()函数
char *s_gets(char *st,int n)
{
char *ret_val;
int i=0;
ret_val=fgets(st,n,stdin);
if(ret_val)
{
while(st[i]!='\n'&&st[i]1='\0')
i++;
if(st[i]=='\n')
st[i]='\0';
else
while(getchar()!='\n')
continue;
}
return ret_val;
}
该函数的好处在于它会自动丢弃stdin中没有读入的内容,因此它也不会影响下一次的输入,并且不会给读入的数组加上换行符而对它进行污染(因为函数中用’\0’来把它替换掉了,这个替换过程我们也可以用strchr函数实现)。
三、字符串的输出
printf()函数
printf("%s",str);
这样就可以啦,注意它不会像puts()函数那样自动加上换行符。
puts()函数
显示字符串直到’\0’,并在结尾添加换行符(但是并不存储在str中)。
puts(str);的效果与printf("%s\n",str);相同,也就是说,输出字符串时puts语句会自动在尾部输出’\n’。
#include<stdio.h>
int main()
{
puts("Hello!");
printf("******\n");
}
运行结果:
Hello!
******
fputs()函数
与puts()函数不同的是,fputs()函数有第二个参数用于指明要写入数据的文件(打印在显示器上就用stdout),并且fputs()函数不像自动那样自动添加换行符,因此它也常与fputs()函数一起使用。
例如:
#include<stdio.h>
int main()
{
char str[100];
while(fgets(str,100,stdin))
fputs(str,stdout);
}
四、小结
缓冲区能解放CPU,提高程序运行速度
---------------------------------------------
gets()函数在stdin中读取数据直到读到换行符,丢弃换行符。
scanf()函数不会读取stdin中的换行符,将换行符留在stdin中。
fgets()函数可能会把键入的回车键读取并存储在字符串中。
gets_s()函数读到换行符时会丢弃它而不是储存它。
---------------------------------------------
puts()函数添加换行符。
fputs()函数不添加换行符。
---------------------------------------------
gets()函数不安全,但是在特定情况下很好用的。
gets_s()函数不推荐使用。
fgets()函数相对好用,我们可以针对使用需求来对它添加一些内容使它的功能变得更强大。
fgets()函数和fputs()函数搭配使用,gets()函数和puts()函数搭配使用。
五、拓展阅读
C语言缓存区详解
scanf函数读取缓冲区数据的问题
缓冲区(buffer)与缓存(cache)
getchar、scanf以及缓冲区的概念