第27章:文件操作基础——文件的打开与关闭
文件操作,说白了就是让程序和磁盘上的数据打交道。我刚开始学C语言时,觉得文件操作特别神秘——明明程序跑完就没了,怎么还能把数据留下来?后来才明白,文件就是程序与外部世界沟通的桥梁。
这一章,咱们就聊聊文件操作最基础的两个函数:fopen和fclose,以及各种读写模式。嗯,别小看这两个函数,我在项目中见过太多因为文件没关好导致数据丢失的惨案。
1. 文件指针:操作文件的"遥控器"
在C语言里,操作文件不是直接读写磁盘,而是通过一个叫FILE的结构体。你可以把它想象成一个遥控器——你按按钮(调用函数),遥控器帮你操作电视(文件)。
FILE *fp; // 声明一个文件指针
fp = fopen("data.txt", "r"); // 打开文件,拿到遥控器
// ... 读写操作 ...
fclose(fp); // 关闭文件,归还遥控器
我个人习惯把文件指针命名为fp,简单明了。当然你也可以用pFile、f之类的,但团队协作时最好统一风格。
2. fopen:打开文件的正确姿势
fopen的原型长这样:
FILE *fopen(const char *filename, const char *mode);
两个参数:文件名和打开模式。返回值是个指针——成功就返回有效的FILE *,失败返回NULL。
FILE *fp = fopen("config.ini", "r");
if (fp == NULL) {
printf("文件打开失败!请检查路径和权限。\n");
return -1;
}
3. 文件读写模式:选对模式,事半功倍
模式参数决定了你能对文件做什么。我整理了一张表,建议你收藏:
| 模式 | 含义 | 文件不存在时 | 文件存在时 |
|---|---|---|---|
"r" |
只读 | 返回NULL | 正常打开,位置在开头 |
"w" |
只写 | 创建新文件 | 清空原有内容 |
"a" |
追加 | 创建新文件 | 保留内容,位置在末尾 |
"r+" |
读写(从开头) | 返回NULL | 正常打开,位置在开头 |
"w+" |
读写(先清空) | 创建新文件 | 清空原有内容 |
"a+" |
读写(追加模式) | 创建新文件 | 保留内容,读在开头,写在末尾 |
核心要点:
"r"系列:文件必须存在,否则失败"w"系列:会清空文件,小心数据丢失"a"系列:不会清空,永远在末尾写
你想想看,如果只是想读日志,用"r"就够了。但如果你不小心用了"w",文件内容瞬间清空——我在调试时犯过这种错,当时真想抽自己一巴掌。
4. 二进制模式 vs 文本模式
在Windows系统上,模式后面可以加b表示二进制模式:
fp = fopen("image.bmp", "rb"); // 二进制读
fp = fopen("data.bin", "wb"); // 二进制写
为什么要区分?说白了,文本模式下,系统会自动处理换行符的转换(\n转\r\n)。而二进制模式不做任何转换,原样读写。
我个人建议:操作图片、音频、可执行文件等非文本文件时,一定用二进制模式。否则文件会损坏。我曾经用文本模式读一个BMP图片,结果文件大小都变了,折腾半天才发现是模式选错了。
5. fclose:用完一定要关
fclose的原型很简单:
int fclose(FILE *stream);
成功返回0,失败返回EOF(通常是-1)。
- 文件内容可能没完全写入磁盘(缓冲区没刷新)
- 文件被占用,其他程序无法访问
- 程序打开的文件数量有限(通常1024个),不关会导致资源耗尽
我曾经遇到一个服务器程序,运行几天后突然打不开新文件了。排查后发现是某个函数里fopen了文件但忘了fclose,文件描述符泄漏了。嗯,从那以后我写代码都会在fopen后面立刻写上对应的fclose。
FILE *fp = fopen("log.txt", "a");
if (fp == NULL) {
perror("打开日志文件失败");
return -1;
}
// ... 写日志 ...
if (fclose(fp) != 0) {
perror("关闭日志文件失败");
return -1;
}
6. 完整示例:读写配置文件
咱们写个完整的例子,读取一个配置文件:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
char buffer[256];
// 打开文件(只读模式)
fp = fopen("config.txt", "r");
if (fp == NULL) {
perror("无法打开config.txt");
return 1;
}
// 读取第一行
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("配置内容: %s", buffer);
}
// 关闭文件
if (fclose(fp) != 0) {
perror("关闭文件失败");
return 1;
}
printf("文件操作完成\n");
return 0;
}
小技巧:用perror代替printf打印错误信息,它会自动显示系统错误原因。比如文件不存在时,会输出:无法打开config.txt: No such file or directory,比你自己写错误信息方便多了。
7. 避坑指南:我踩过的那些坑
- 路径问题:Windows用反斜杠
\,但在C字符串里要写成\\。或者直接用正斜杠/,Windows也支持。 - 权限问题:在Linux下,如果文件权限不够,fopen会返回NULL。记得检查errno。
- 缓冲区问题:写文件后没调用fclose或fflush,数据可能还在缓冲区里。程序崩溃时数据就丢了。
- 多次打开:同一个文件可以多次打开,但每次打开都会获得独立的文件指针和位置指示器。
8. 知识体系总览
下面这张图帮你理清本章的核心逻辑:
这张图把文件操作的流程串起来了。记住核心原则:打开必检查,用完必关闭。这两句话,能帮你避开90%的文件操作坑。
好了,文件打开与关闭的基础就聊到这儿。下一章咱们会深入文件读写函数——fgetc、fputc、fgets、fputs、fread、fwrite,到时候你会看到,文件操作其实就那么几板斧。