一,流与缓冲
流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是无缓冲的,这样
也能保证错误提示和输出能及时地反馈给用户,供用户排除错误。
二,基于文件流的操作
常用函数:
- <SPAN style="FONT-SIZE: 18px"><STRONG>打开和关闭流
- #include<stdio.h>
- FILE*fopen(const char * restrict pathname,const char*restrict type);
- FILE*fdopen(int fileds,const char*type);</STRONG></SPAN>
<span style="font-size:18px;"><strong>打开和关闭流
#include<stdio.h>
FILE*fopen(const char * restrict pathname,const char*restrict type);
FILE*fdopen(int fileds,const char*type);</strong></span>
fopen函数的第一个参数表示需要打开的文件的路径,第二个参数表示打开的方式。
fdopen函数用于在一个已经打开的文件上建立一个流,第一个参数是已打开文件的文件描述符,第二个参数是与fopen函数的第二个参数一样。只有一点不同的是,由于文件已经打开,所以fdopen函数不会再创建新文件,而且也不会将文件截短为0,这一点要热别注意,这两点在打开文件描述符的时候已经完成。
Type说明如下:
type | 文件类型 | 是否新建 | 是否清空 | 可读 | 可写 | 读写位置 |
r | 文本文件 | NO | NO | YES | NO | 文件开头 |
r+ | 文本文件 | NO | NO | YES | YES | 文件开头 |
w | 文本文件 | YES | YES | NO | YES | 文件开头 |
w+ | 文本文件 | YES | YES | YES | YES | 文件开头 |
a | 文本文件 | NO | NO | NO | YES | 文件结尾 |
a+ | 文本文件 | YES | NO | YES | YES | 文件结尾 |
rb | 二进制文件 | NO | NO | YES | NO | 文件开头 |
r+b或rb+ | 二进制文件 | NO | NO | YES | YES | 文件开头 |
wb | 二进制文件 | YES | YES | NO | YES | 文件开头 |
w+b或wb+ | 二进制文件 | YES | YES | YES | YES | 文件开头 |
ab | 二进制文件 | NO | NO | NO | YES | 文件结尾 |
a+b或ab+ | 二进制文件 | YES | NO | YES | YES | 文件结尾 |
Linux里用fclose函数关闭一个文件流,函数原型如下:
- #include<stdio.h>
- int fclose(FILE *fp);
#include<stdio.h>
int fclose(FILE *fp);
如果执行成功,函数返回0,失败返回EOF,这个值在定义在stdio.h中,其值为-1。fclose函数关闭文件时,该函数会将保存在内存中未来得及写回到磁盘的文件内容写回到磁盘上。了解这一点很重要,如果没有调用fclose函数,就必 须等待内存中缓冲区被填满,由系统将其内容写回到磁盘上去。对于fclose函数是否需要检查返回值的问题困扰着许多程序员。虽然严格地说应该检查所有的 系统调用的返回值,并且进行错误处理,但对于fclose函数出错的几率很小,几乎为0.但如果去关闭一个网络环境中的远程文件,fclose函数就有可 能出错。由于fclose函数在关闭文件时会将缓冲区的内容写回到磁盘上,因此fclose函数实际是进行了一个写操作。在网络环境中,文件的内容是要通 过网络传输到目的主机上并写入磁盘上的。在这个传输过程中,如果网络链接出现问题或者传输数据出错,就会导致文件内容写入失败。这时fclose函数就会 出错。由此可知,如果在本地关闭一个文件可以不用检查返回值;如果在网络环境中关闭一个文件,检查fclose函数的返回值是有必要的。
错误和EOF
它是end of file的缩写,表示"文字流"(stream)的结尾。这里的"文字流",可以是文件(file),也可以是标准输入(stdin)。
比如,下面这段代码就表示,如果不是文件结尾,就把文件的内容复制到屏幕上。
- int c;
- while ((c = fgetc(fp)) != EOF) {
- putchar (c);
- }
int c;
while ((c = fgetc(fp)) != EOF) {
putchar (c);
}
很自然地,我就以为,每个文件的结尾处,有一个叫做EOF的特殊字符,读取到这个字符,操作系统就认为文件结束了。
但是,后来我发现,EOF不是特殊字符,而是一个定义在头文件stdio.h的常量,一般等于-1。
- #define EOF (-1)
#define EOF (-1)
于是,我就困惑了。
如果EOF是一个特殊字符,那么假定每个文本文件的结尾都有一个EOF(也就是-1),还是可以做到的,因为文本对应的ASCII码都是正值,不可能有负值。但是,二进制文件怎么办呢?怎么处理文件内部包含的-1呢?
这个问题让我想了很久,后来查了资料才知道,在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结尾,所返回的一个信号值(也就是-1)。至于系统怎么知道文件的结尾,资料上说是通过比较文件的长度。
所以,处理文件可以写成下面这样:
- int c;
- while ((c = fgetc(fp)) != EOF) {
- do something
- }
int c;
while ((c = fgetc(fp)) != EOF) {
do something
}
这样写有一个问题。fgetc()不仅是遇到文件结尾时返回EOF,而且当发生错误时,也会返回EOF。因此,C语言又提供了feof()函数,用来保证确实是到了文件结尾。上面的代码feof()版本的写法就是:
- int c;
- while (!feof(fp)) {
- c = fgetc(fp);
- do something;
- }
int c;
while (!feof(fp)) {
c = fgetc(fp);
do something;
}
但是,这样写也有问题。fgetc()读取文件的最后一个字符以后,C语言的feof()函数依然返回0,表明没有到达文件结尾;只有当fgetc()向后再读取一个字符(即越过最后一个字符),feof()才会返回一个非零值,表示到达文件结尾。
所以,按照上面这样写法,如果一个文件含有n个字符,那么while循环的内部操作会运行n+1次。所以,最保险的写法是像下面这样:
- int c = fgetc(fp);
- while (c != EOF) {
- do something;
- c = fgetc(fp);
- }
- if (feof(fp)) {
- printf("\n End of file reached.");
- } else {
- printf("\n Something went wrong.");
- }
int c = fgetc(fp);
while (c != EOF) {
do something;
c = fgetc(fp);
}
if (feof(fp)) {
printf("\n End of file reached.");
} else {
printf("\n Something went wrong.");
}
除了表示文件结尾,EOF还可以表示标准输入的结尾。
- int c;
- while ((c = getchar()) != EOF) {
- putchar(c);
- }
int c;
while ((c = getchar()) != EOF) {
putchar(c);
}
但是,标准输入与文件不一样,无法事先知道输入的长度,必须手动输入一个字符,表示到达EOF。
Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出"标准输入"的缓存区,所以这时必须按两次Ctrl-D);Windows中,Ctrl-Z表示EOF。(顺便提一句,Linux中按下Ctrl-Z,表示将该进程中断,在后台挂起,用fg命令可以重新切回到前台;按下Ctrl-C表示终止该进程。)
那么,如果真的想输入Ctrl-D怎么办?这时必须先按下Ctrl-V,然后就可以输入Ctrl-D,系统就不会认为这是EOF信号。Ctrl-V表示按"字面含义"解读下一个输入,要是想按"字面含义"输入Ctrl-V,连续输入两次就行了。