第27章:文件操作基础——文件的打开与关闭

文件操作,说白了就是让程序和磁盘上的数据打交道。我刚开始学C语言时,觉得文件操作特别神秘——明明程序跑完就没了,怎么还能把数据留下来?后来才明白,文件就是程序与外部世界沟通的桥梁。

这一章,咱们就聊聊文件操作最基础的两个函数:fopenfclose,以及各种读写模式。嗯,别小看这两个函数,我在项目中见过太多因为文件没关好导致数据丢失的惨案。

1. 文件指针:操作文件的"遥控器"

在C语言里,操作文件不是直接读写磁盘,而是通过一个叫FILE的结构体。你可以把它想象成一个遥控器——你按按钮(调用函数),遥控器帮你操作电视(文件)。

FILE *fp;  // 声明一个文件指针
fp = fopen("data.txt", "r");  // 打开文件,拿到遥控器
// ... 读写操作 ...
fclose(fp);  // 关闭文件,归还遥控器

我个人习惯把文件指针命名为fp,简单明了。当然你也可以用pFilef之类的,但团队协作时最好统一风格。

2. fopen:打开文件的正确姿势

fopen的原型长这样:

FILE *fopen(const char *filename, const char *mode);

两个参数:文件名和打开模式。返回值是个指针——成功就返回有效的FILE *,失败返回NULL

重要提醒:每次调用fopen后,必须检查返回值是否为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. 知识体系总览

下面这张图帮你理清本章的核心逻辑:

文件操作核心流程 fopen 打开文件 检查返回值是否为NULL 失败:处理错误 成功:执行读写操作 fclose 关闭文件 读写模式 r / w / a r+ / w+ / a+ rb / wb / ab rb+ / wb+ / ab+ (加b为二进制模式) 核心原则:打开必检查,用完必关闭

这张图把文件操作的流程串起来了。记住核心原则:打开必检查,用完必关闭。这两句话,能帮你避开90%的文件操作坑。


好了,文件打开与关闭的基础就聊到这儿。下一章咱们会深入文件读写函数——fgetcfputcfgetsfputsfreadfwrite,到时候你会看到,文件操作其实就那么几板斧。

专注资料整理