一. 流与缓冲
流I/O是由C语言的标准函数提供的,这些I/O可以替代系统中提供的read和write函数。事实上流I/O的内部封装了这两个基本的文件读写系统调用。使用流I/O在某些程度上来讲要方便一些,这些I/O在效率上没有特别大的差异。
基于流的操作最终会调用read或者write函数进行操作。为了使程序的运行效率最高,流对象通常会提供缓冲区,以减少调用系统I/O库函数的次数。
基于流的I/O提供以下2种缓冲:
1. 全缓冲:直到缓冲区填满,才调用系统I/O函数。对于读操作来说,直到读入的内容的字节数等于缓冲区大小或者文件以经到达结尾,才进行I/O操作将外存文件内容读入缓冲区;对于写操作来说,直到缓冲区填满,才进行实际的I/O操作将缓冲区内容写到外存文件中。磁盘文件通常是全缓冲的。
2. 行缓冲:直到遇到换行符\n才调用系统I/O函数。对于读操作来说,遇到换行符\n才进行I/O操作,将所读内容写入缓冲区;对于写操作来说,遇到换行符\n才进行I/O操作,将缓冲区内容写到外存。由于缓冲区大小是有限制的,所以当缓冲区填满时即使没有遇到\n,也同样会进行实际的I/O操作。标准输入stdin和标准输出stdout都默认是行缓冲的。
3. 无缓冲:没有缓冲区,数据会立即读入或者输出到外存文件和设备上。标准出错stderr是无缓冲的,这样也能保证错误提示和输出能及时地反馈给用户,供用户排除错误。
二. 基于文件流的操作
常用函数:
1. 打开和关闭流
#include
FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fileds, const char *mode);
fopen函数的第一个参数表示要打开的文件的路径,第二个参数表示打开的方式。
fdopen函数用于在一个已经打开的文件上建立一个流,第一个参数是已打开文件的文件描述符,第二个参数是与fopen函数的第二个参数一样。只有一点不同的是,由于文件已经打开,所以fdopen函数不会再创建新文件,而且也不会将文件截短为0,要特别注意,这两点在打开文件描述符的时候已经完成。
Linux里用fclose函数关闭一个文件流,函数原型如下:
#include
int fclose(FILE *fp);
如果执行成功,函数返回0,失败返回EOF,EOF定义在stdio.h中,其值为-1。fclose函数关闭文件时,该函数会将保存在内存中未来得及写回到磁盘的文件内容写回到磁盘上。了解这一点很重要,如果没有调用fclose函数,就必须等待内存中缓冲区被填满,由系统将其内容写回到磁盘上去。对于fclose函数是否需要检查返回值的问题困扰着许多程序员。虽然严格地说应该检查所有的系统调用的返回值,并且进行错误处理,但对于fclose函数出错的几率很小,几乎为0。但如果去关闭一个网络环境中的远程文件,fclose函数就有可能出错。由于fclose函数在关闭文件时会将缓冲区的内容写回到磁盘上,因此fclose函数实际是进行了一个写操作。在网络环境中,文件的内容是要通 过网络传输到目的主机上并写入磁盘上的。在这个传输过程中,如果网络链接出现问题或者传输数据出错,就会导致文件内容写入失败。这时fclose函数就会出错。由此可知,如果在本地关闭一个文件可以不用检查返回值;如果在网络环境中关闭一个文件,检查fclose函数的返回值是有必要的。
三. 以字符为单位读写数据
每次读写一个字符数据的I/O方式称为每次一个字符的I/O。
Linux下使用fgetc函数获得一个字符,其函数原型如下:
#include
int fgetc(FILE *fp);
函数如果执行成功则返回该字符的ASCLL值,如果执行失败,则返回EOF。
Linux下使用fputc函数输出一个字符,函数原型如下:
#include
int fputc(int c, FILE *fp)
第一个参数表示想要输出的字符的ASCLL值(源),第二个参数表示想要输出的文件流(目的地)。
四. 以行为单位读写数据
当输入内容遇到\n时则将流中\n之前的内容送到缓冲区中的I/O方式称为每一次行的I/O。
Linux使用下列函数提供一次读入一行的功能。
#include
char *fgets(char *str, int size, FILE *stream);
char *gets(char *);
fgets函数的第一个参数表示存放读入的缓冲区,第二个参数n表示读入的字符个数,此参数的最大值不能超过缓冲区的长度。fgets函数一直读,直到遇到\n为止,如果在n-1个字符內未遇到换行符,则只读入n-1个字符。最后一个字符用于存储字符串结束标志\0.需要注意的是fgets函数会将‘\n’换行符也读进缓冲区中,因此缓冲区的实际有效内容应该是缓冲区实际字节数(不包括‘\0’)减1.fgets函数的第三个参数是需要读入的流对象。
fgets()用来从参数stream所指的文件内读入字符并存到参数str所指的内存空间,直到出现换行字符、读到文件尾或是已读了size-1个字符为止,最后会加上NULL作为字符串结束。
fgets函数的返回值有以下两种情况:
1. 成功读取一行,返回缓冲区的首地址。
2. 读取出错或者文件已经到达结尾则返回NULL。
gets函数和fgets函数类似,该函数从标准输入流中读取一行并将其存入一个缓冲区,并不将‘\n’读进缓冲区中。gets函数的返回值和fgets相同。
Linux 下用fputs函数和puts函数实现输出一行字符串,其函数原型如下:
#include
int fputs(const char *str, FILE *fp);
int puts(const char *str);
fputs函数的第一个参数表示存放输出内容的缓冲区,第二个参数表示要输出的文件。如果执行成功则返回输出的字节数,失败返回-1。
puts函数用与向标准输出输出一行字符串,其参数和fputs函数的第一个参数相同,如果成功输出,则返回输出的字节数,失败则返回-1。
值得注意的是,虽然gets函数不读入\n,但是puts函数却自动输出\n。fputs和puts函数都不输出字符串的结束符‘\0’。
对于I/O来说,fputs函数和fgets函数的搭配是安全又可靠的。
五. gets函数的漏洞
gets函数和fgets函数最大的不同是gets函数的缓冲区虽然由用户提供,但是用户无法指定其一次最多读入多少个字节的内容。这一点导致gets函数变成了一个危险的函数。
六. 关于scanf()、gets()、fgets()输入字符串的区别
scanf()
scanf输入字符串时不能输入回车,空格和制表符。以回车结束输入(遇空格或制表符输入不结束),回车,空格和制表符残留在stdin流中,没有被读到字符串中,在scanf语句后可用getchar( )把'\n',空格或制表符接收掉(清空stdin流)。
gets()
gets能输入空格和制表符,不能输入回车。以回车结束输入,空格和制表符输入到字符串中作为有效字符,回车不作为读取的内容,输入的回车符没有残留在stdin流中,而是被转换为NULL,即被丢弃,最后在字符串末尾添加 '\0' 。
fgets()
fgets能输入回车,空格和制表符。以回车结束输入,回车符、空格和制表符均输入到字符串中作为有效字符,最后在回车符'\n'后面加'\0'。
scanf(),gets(),fgets()都是从stdin流中读取字符,注意:当键盘输入一串东西时,这3个函数并未开始工作,只有当打入Enter键时它们才到stdin流中读取。gets读取速度快于scanf。
另外,getch()也能实现输入一串字符,但是getch()不同于上面3个函数,getch()不是从stdin流中读取,而是直接从键盘缓冲区读取, 它不必等待Enter键,键盘按一下,它读入一个字符,包括回车、空格、制表符,甚至Backspace键,都能读到。