# 打开和关闭文件 在C语言中,操作文件之前必须先打开文件;所谓“打开文件”,就是让程序和文件建立连接的过程。 打开文件之后,程序可以得到文件的相关信息,例如大小、类型、权限、创建者、更新时间等。在后续读写文件的过程中,程序还可以记录当前读写到了哪个位置,下次可以在此基础上继续操作。 > 标准输入文件 stdin(表示键盘)、标准输出文件 stdout(表示显示器)、标准错误文件 stderr(表示显示器)是由系统打开的,可直接使用。 使用 头文件中的 fopen() 函数即可打开文件,它的用法为: FILE *fopen(char *filename, char *mode); `filename`为文件名(包括文件路径),`mode`为打开方式,它们都是字符串。 ## fopen()函数 fopen() 会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。 FILE 是 头文件中的一个结构体,它专门用来保存文件信息。我们不用关心 FILE 的具体结构,只需要知道它的用法就行。 如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如: ```c FILE *fp = fopen("demo.txt", "r"); ``` 表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针。 ```c FILE *fp = fopen("D:\\demo.txt", "rb+"); ``` 表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写。 **判断文件是否打开成功** 打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,请看下面的代码: ```c FILE *fp; if((fp=fopen("D:\\demo.txt", "rb")) == NULL){ printf("Fail to open file!\n"); exit(0); //退出程序 } ``` 我们通过判断 fopen() 的返回值是否和 NULL 相等来判断是否打开失败:如果 fopen() 的返回值为 NULL,那么 fp 的值也为 NULL,此时 if 的判断条件成立,表示文件打开失败。 以上代码是文件操作的规范写法,读者在打开文件时一定要判断文件是否打开成功,因为一旦打开失败,后续操作就都没法进行了,往往以“结束程序”告终。 ## fopen() 函数的打开方式 不同的操作需要不同的文件权限。例如,只想读取文件中的数据的话,“只读”权限就够了;既想读取又想写入数据的话,“读写”权限就是必须的了。 另外,文件也有不同的类型,按照数据的存储方式可以分为二进制文件和文本文件,它们的操作细节是不同的。 在调用 fopen() 函数时,这些信息都必须提供,称为“文件打开方式”。最基本的文件打开方式有以下几种: **控制读写权限的字符串(必须指明)** | 打开方式 | 说明 | | -------- | ------------------------------------------------------------ | | "r" | 以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。 | | "w" | 以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 | | "a" | 以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 | | "r+" | 以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。 | | "w+" | 以“写入/更新”方式打开文件,相当于`w`和`r+`叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。 | | "a+" | 以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。 | **控制读写方式的字符串(可以不写)** | 打开方式 | 说明 | | -------- | --------------------------------- | | "t" | 文本文件。如果不写,默认为`"t"`。 | | "b" | 二进制文件。 | 调用 fopen() 函数时必须指明读写权限,但是可以不指明读写方式(此时默认为`"t"`)。 读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(换句话说,不能将读写方式放在读写权限的开头)。例如: - 将读写方式放在读写权限的末尾:"rb"、"wt"、"ab"、"r+b"、"w+t"、"a+t" - 将读写方式放在读写权限的中间:"rb+"、"wt+"、"ab+" 整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是: - r(read):读 - w(write):写 - a(append):追加 - t(text):文本文件 - b(binary):二进制文件 - +:读和写 ## 关闭文件 文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的用法为: ```c int fclose(FILE *fp); //fp 为文件指针。例如: fclose(fp); //文件正常关闭时,fclose() 的返回值为0,如果返回非零值则表示有错误发生。 ``` ## 实例 一行一行地读取文本文件的所有内容。 ```c #include #include #define N 300 int main() { FILE *fp; char str[N + 1]; //判断文件是否打开失败 if ((fp = fopen("d:\\Data\\C++\\Cpp\\Project7\\Debug\\demo.txt", "rt")) == NULL) { printf("Fail to open file!"); exit(0); } //循环读取数据的每一行数据 while (fgets(str, N, fp) != NULL) { printf("%s", str); } //读取后记得关闭文件 fclose(fp); return 0; } ``` ## 文本文件和二进制文件的区别 根据我们以往的经验,文本文件通常用来保存肉眼可见的字符,比如`.txt`文件、`.c`文件、`.dat`文件等,用文本编辑器打开这些文件,我们能够顺利看懂文件的内容。 二进制文件通常用来保存视频、图片、程序等不可阅读的内容,用文本编辑器打开这些文件,会看到一堆乱码,根本看不懂。 但是从物理上讲,二进制文件和字符文件并没有什么区别,它们都是以二进制的形式保存在磁盘上的数据。 我们之所以能看懂文本文件的内容,是因为文本文件中采用的是 ASCII、UTF-8、GBK 等字符编码,文本编辑器可以识别出这些编码格式,并将编码值转换成字符展示出来。 而二进制文件使用的是 mp4、gif、exe 等特殊编码格式,文本编辑器并不认识这些编码格式,只能按照字符编码格式胡乱解析,所以就成了一堆乱七八糟的字符,有的甚至都没见过。 如果我们新建一个 mp4 文件,给它写入一串字符,然后再用文本编辑器打开,你一样可以读得懂,有兴趣的读者可以自己试试。 总起来说,不同类型的文件有不同的编码格式,必须使用对应的程序(软件)才能正确解析,否则就是一堆乱码,或者无法使用。 ## fopen()中文本方式和二进制方式 在C语言中,二进制方式很简单,读取文件时,会原封不动的读出文件的全部內容,写入数据时,也是把缓冲区中的內容原封不动的写到文件中。 文本方式和二进制方式并没有本质上的区别,只是对于换行符的处理不同。 C语言程序将`\n`作为换行符,类 UNIX/Linux 系统在处理文本文件时也将`\n`作为换行符,所以程序中的数据会原封不动地写入文本文件中,反之亦然。 但是 Windows 系统却不同,它将`\r\n`作为文本文件的换行符。 在 Windows 系统中,如果以文本方式打开文件,当读取文件时,程序会将文件中所有的`\r\n`转换成一个字符`\n`。也就是说,如果文本文件中有连续的两个字符是`\r\n`,则程序会丢弃前面的`\r`,只读入`\n`。 当写入文件时,程序会将`\n`转换成`\r\n`写入。也就是说,如果要写入的内容中有字符`\n`,则在写入该字符前,程序会自动先写入一个`\r`。 因此,如果用文本方式打开二进制文件进行读写,读写的内容就可能和文件的内容有出入。 总起来说,对于 Windows 平台,为了保险起见,我们最好用`"t"`来打开文本文件,用`"b"`来打开二进制文件。对于 Linux 平台,使用`"r"`还是`"b"`都无所谓,既然默认是`"r"`,那我们什么都不写就行了。