7.5 文件访问
到目前为止,我们讨论的例子都是从标准输入读取数据,并向标准输出输出数据
标准输入和标准输出是操作系统自动提供给程序访问的
接下来,我们编写一个访问文件的程序,且它所访问的文件还没有连接到该程序
程序 cat
可以用来说明该问题,它把一批命名文件串联后输出到标准输出上
cat
可用来在屏幕上打印文件,对于那些无法通过名字访问文件的程序来说,它还可以用作通用的输入收集器
例如下列命令行:
cat x.c y.c
将在标准输出上打印文件 x.c
和 y.c
的内容
如何设计命名文件的读取过程呢?即如何将用户需要使用的文件的外部名同读取数据的语句关联起来
在读写一个文件之前,必须通过库函数 fopen
打开该文件
fopen
用类似于 x.c
或 y.c
这样的外部名与操作系统进行某些必要的连接和通信,并返回一个随后可以用于文件读写操作的指针
该指针称为文件指针,它指向一个包含文件信息的结构,这些信息包括:
缓冲区的位置、缓冲区中当前字符的位置、文件的读或写状态、是否出错或是否已经到达文件结尾等等
用户不必关心这些细节,因为 <stdio.h>
中已经定义了一个包含这些信息的结构 FILE
在程序中只需按照下列方式声明一个文件指针即可:
FILE *fp;
FILE *fopen(char *name, char *mode);
在本例中,fp
是一个指向结构 FILE
的指针,并且,fopen
函数返回一个指向结构 FILE
的指针
FILE
是像 int
一样的类型名,不是结构标记,它是通过 typedef
定义的
UNIX 系统中 fopen
的实现细节将在 8.5 节中讨论
在程序中,可以这样调用 fopen
函数:
fp = fopen(name, mode);
fopen
的第一个参数是一个字符串,它包含文件名
第二个参数是访问模式,也是一个字符串,用于指定文件的使用方式,允许的模式包括:读("r"
)、写("w"
)及追加("a"
)
某些系统还区分文本文件和二进制文件,对后者的访问需要在模式字符串中增加字符 "b"
如果打开一个不存在的文件用于写或追加,该文件将被创建(如果可能的话)
当以写方式打开一个已存在的文件时,该文件原来的内容将被覆盖
如果以追加方式打开一个文件,则该文件原来的内容将保留不变
读一个不存在的文件会导致错误,其它一些操作也可能导致错误,比如试图读取一个无读取权限的文件
如果发生错误,fopen
将返回 NULL
可以更进一步地定位错误的类型,具体方法请参见附录 B.1 节中关于错误处理函数的讨论
文件被打开后,就需要考虑采用哪种方法对文件进行读写
有多种方法可供考虑,其中 getc
和 putc
函数最为简单
getc
从文件中返回下一个字符,它需要知道文件指针,以确定对哪个文件执行操作:
int getc(FILE *fp)
getc
函数返回 fp
指向的输入流中的下一个字符
如果到达文件尾或出现错误,该函数将返回 EOF
putc
是一个输出函数,如下所示:
int putc(int c, FILE *fp)
该函数将字符 c
写入到 fp
指向的文件中,并返回写入的字符
如果发生错误,则返回 EOF
类似于 getchar
和 putchar
,getc
和 putc
是宏而不是函数
启动一个 C 语言程序时,操作系统环境负责打开 3 个文件,并将这 3 个文件的指针提供给该程序
这 3 个文件分别是标准输入、标准输出、标准错误
相应的文件指针分别为 stdin
、stdout
、stderr
,它们在 <stdio.h>
中声明
在大多数环境中,stdin
指向键盘,而 stdout
和 stderr
指向显示器
我们从 7.1 节的讨论中可以知道,stdin
和 stdout
可以被重定向到文件或管道
getchar
和 putchar
函数可以通过 getc
、putc
、stdin
及 stdout
定义如下:
#define getchar() getc(stdin)
#define putchar(c) putc((c), stdout)
对于文件的格式化输入或输出,可以使用函数 fscanf
和 fprintf
它们与 scanf
和 printf
函数的区别仅仅在于它们的第一个参数是一个指向所要读写的文件的指针,第二个参数是格式串:
int fscanf(FILE *fp, char *format, ...)
int fprintf(FILE *fp, char *format, ...)
掌握这些预备知识之后,我们现在就可以编写出将多个文件连接起来的 cat
程序了
如果有命令行参数,参数将被解释为文件名,并按顺序逐个处理,如果没有参数,则处理标准输入
#include <stdio.h>
/* cat: concatenate files, version 1 */
main(int argc, char *argv[])
{
FILE *fp;
void filecopy(FILE *, FILE *)
if (argc == 1) /* no args; copy standard input */
filecopy(stdin, stdout);
else
while(--argc > 0)
if ((fp = fopen(*++argv, "r")) == NULL) {
printf("cat: can't open %s\n, *argv);
return 1;
} else {
filecopy(fp, stdout);
fclose(fp);
}
return 0;
}
/* filecopy: copy file ifp to file ofp */
void filecopy(FILE *ifp, FILE *ofp)
{
int c;
while ((c = getc(ifp)) != EOF)
putc(c, ofp);
}
文件指针 stdin
与 stdout
都是 FILE *
类型的对象,但它们是常量,而非变量,因此不能对它们赋值
函数 int fclose(FILE *fp)
执行和 fopen
相反的操作
它断开由 fopen
函数建立的文件指针和外部名之间的连接,并释放文件指针以供其它文件使用
因为大多数操作系统都限制了一个程序可以同时打开的文件数,所以,当文件指针不再需要时就应该释放,这是一个好的编程习惯
对输出文件执行 fclose
还有另外一个原因:它将把缓冲区中由 putc
函数正在收集的输出写到文件中
当程序正常终止时,程序会自动为每个打开的文件调用 fclose
函数
如果不需要使用 stdin
与 stdout
,可以把它们关闭掉,也可以通过库函数 freopen
重新指定它们