文件代表一系列的字节。函数 fopen()将一个文件和一个流关联起来,并初始化一个类型为 FILE 的对象,该对象包含了控制该流的所有信息。这些信息包括指向缓冲区的指针;文件位置指示器,它指定了获取文件的位置;以及指示错误和文件结尾情况的标志。
每个用于打开文件的函数(也就是 fopen()、freopen()和 tmpfile())都会返回一个指向 FILE 对象的指针,该 FILE 对象包含与被打开文件相关联的流。一旦打开了文件,就可以调用函数传递数据并对流进行处理。这些函数都把指向 FILE 对象的指针(通常称为 FILE 指针)作为它们的参数之一。FILE 指针指定了正在进行操作的流。
I/O 链接库也包含了用于操作文件系统的函数,这些函数把文件名作为它们的参数之一。使用这些函数不需要事先打开文件。它们包括:
(1) 函数 remove()删除一个文件(或者空目录)。该字符串参数是文件名。如果文件具有多个名称,那么 remove()只会删除所指定的名称,而非删除文件本身。该文件数据还可以通过别的方式来获取,但是不能通过已删除的文件名访问。
(2) 函数 rename()改变一个文件(或目录)的名称。该函数的两个字符串参数依次为旧文件名和新文件名。函数 remove()和 rename()的返回值类型都是 int,成功时都会返回 0,失败时都会返回非 0值,下面的语句将 songs.dat 重命名为 mysong.dat:
if ( rename( "songs.dat", "mysongs.dat" ) != 0 )
fprintf( stderr, "Error renaming \"songs.dat\".\n );
导致函数 rename()失败的原因包括:使用旧文件名的文件不存在;程序获取文件的权限不够;或者文件已经被打开。至于具体何种格式的文件名才是合法的,这是由实现版本决定。
无论是新文件或已有文件,首先必须打开该文件,才可以向文件中写入数据,或者修改其中的内容。打开一个文件时,必须指定访问模式(access mode),以表明计划对该文件进行的是读、写或读写结合等操作。当使用完该文件后,必须关闭它以释放资源。
标准库提供函数 fopen()用以打开文件(在特殊情况下,还可以使用函数 freopen()和 tmpfile()来打开文件):
FILE *fopen( const char * restrict filename,
const char * restrict mode );
字符串 filename 向该函数传入所需打开的文件的名称。该文件名字符串也可以包含目录信息,但必须保证字符串长度不得超过宏 FILENAME_MAX 中指定的最大长度。函数的第二个参数 mode 也是一个字符串,用来指定文件访问模式。函数 freopen()会把文件与一个新的流关联起来。
FILE *freopen(const char * restrict filename,
const char * restrict mode,
FILE * restrict stream );
该函数将一个流重新定向。与 fopen()类似,freopen()也会用指定的访问模式打开指定的文件。但不同的是,freopen()不会建立新的流,而是将文件与已有的流关联,已有的流通过该函数的第三个参数指定。之前与该流关联的文件会被关闭。freopen()常被用来重新定向到标准流 stdin、stdout 和 stderr。
FILE *tmpfile( void );
函数 tmpfile()会建立一个新的临时文件,其文件名与所有已有文件名都不一样,然后打开该文件,进行二进制数据的读写操作(类似于函数 fopen()采用“wb+”访问模式)。如果该程序正常地结束,该文件会被自动删除。
所有三个打开文件的函数 fopen()、freopen()和 tmpfile(),都会返回一个指针。如果成功,该指针就指向已打开的流,如果失败,该指针就为空指针。
如果一个文件打开用于写操作,程序应赋予其独立访问权限以防止其他程序同时对该文件进行写操作。传统的标准函数并不能确保独立文件访问权限,但是 C11 新增的三个新“安全”函数 fopen_s()、freopen_s()和 tmpfile_s(),在操作系统支持的前提下,可以提供独立访问权限。
函数 fopen()和 freopen()的第二个参数指定了文件的访问模式,访问模式决定了流所许可的输入和输出操作。对访问模式字符串的许可值有严格的限制。该字符串的第一个字符只能为三种形式:r(表示“read”)、w(表示“write”)或者 a(表示“append”)。
在最简单情况下,该字符串只包含一个字符。模式字符串还可以包含 + 和 b(如果两者同时具有,次序是没有关系的,+b 效果等同于 b+)。
模式字符串中的加号(+)表示读写操作都可以进行。然而,程序不可以在读操作和写操作之间立即作切换。在写操作之后,必须调用函数 fflush()或者定位函数(fseek()、fsetpos()或 rewind()),然后才可以执行读操作。在读操作之后,必须调用定位函数,然后才可以执行写操作。
模式字符串中的 b 表示文件以二进制模式打开。也就是说,与该文件关联的流是二进制流。如果模式字符串中没有 b,新建立的流就是字符串流。
当模式字符串以 r 开始时,该文件必须已经存在于文件系统中。当模式字符串以 w 开始时,如果文件不存在,则会建立一个新文件;如果文件存在,该文件当前内容会被清除,因为在“write”模式中,函数 fopen()将文件长度设置为 0。
C11 新增一个功能,在操作系统支持的前提下,允许在独立写操作模式下打开文件。可以在以 w 起始的模式字符串中使用后缀 x,例如 wx 或 w+bx,以指定独立访问权限。如果文件已经存在或者不能被创建,则文件打开函数执行失败(返回空指针)。否则,将创建文件并以独立访问权限打开它。
当模式字符串以 a 开始时,如果文件不存在,则也会建立一个新文件。如果文件存在,该文件当前内容会被保留,因为所有新写入的内容都会从文件尾端添加。下面是一个简单的示例:
#include <stdio.h>
#include <stdbool.h>
_Bool isReadWriteable( const char *filename )
{
FILE *fp = fopen( filename, "r+" ); // 打开一个文件以用于读写
if ( fp != NULL ) // fopen()是否执行成功
{
fclose(fp); // 成功:关闭文件,没有错误需要处理
return true;
}
else // 失败
return false;
}
上例也展示了如何利用函数 fclose()关闭一个文件。
关闭文件时需要使用函数 fclose(),该函数的原型是:
int fclose( FILE *fp );
该函数把缓冲区内存在的所有数据保存到文件中,关闭文件,释放所有用于该流输入输出缓冲区的内存。函数 fclose()返回 0 表示成功,返回 EOF 表示产生错误。
当程序退出时,所有打开的文件都会自动关闭。尽管如此,还是应该在完成文件处理后,主动关闭文件。否则,一旦遇到非正常的程序终止,就可能会丢失数据。而且,一个程序可以同时打开的文件数量是有限的,数量上限小于等于常量 FOPEN_MAX 的值。