C 语言文件操作完全指南:从打开到关闭

C 语言文件操作完全指南:从打开到关闭

文件操作是 C 语言从"小黑框计算器"迈向"能存数据的实用程序"的关键一步。本文将系统梳理 C 标准库中的文件 I/O 函数,以及与之紧密配合的字符串格式化函数,帮你建立完整的输入输出知识体系。


一、文件操作的基本流程

无论读写什么文件,核心流程永远是四步:

plaintext 复制代码
打开文件 → 读写数据 → 关闭文件 → 指针置空

对应四个核心环节:

步骤 函数 作用
打开 fopen 建立程序和文件的连接
关闭 fclose 断开连接,释放资源
检测 ferror / feof 判断读写过程中是否出错或到达末尾
善后 fp = NULL 防止野指针

二、打开与关闭:fopen 与 fclose

2.1 fopen 函数

c 复制代码
FILE *fp = fopen("文件名", "打开模式");

打开模式是重点,也是最容易踩坑的地方:

模式 含义 文件不存在 文件存在
"r" 只读(文本) 返回 NULL 从开头读
"w" 只写(文本) 创建新文件 清空原内容
"a" 追加(文本) 创建新文件 在末尾追加
"r+" 读写(文本) 返回 NULL 从开头读写
"w+" 读写(文本) 创建新文件 清空原内容
"a+" 读写追加(文本) 创建新文件 读从头,写从尾
b 二进制模式 --- 不转换换行符

关键记忆点:

  • w 的模式会清空文件,慎用
  • + 表示既能读又能写
  • b 表示按原始字节处理,不做换行符转换(Windows 下尤其重要)

2.2 标准错误处理模板

c 复制代码
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
    perror("打开文件失败");  // 自动打印系统错误原因
    return 1;
}
// ... 读写操作 ...
fclose(fp);
fp = NULL;  // 好习惯:防止悬空指针

2.3 fclose 的重要性

操作系统对单个程序能同时打开的文件数量有限制(通常是几百到几千个)。忘记 fclose 会导致:

  • 资源泄漏:后续无法打开新文件
  • 数据丢失:缓冲区里的内容可能没来得及写入磁盘

三、文本读写:字符与字符串

3.1 单字符读写:fgetc 与 fputc

c 复制代码
// 逐字符读取文件
FILE *fp = fopen("test.txt", "r");
if (fp == NULL) {
    perror("fopen");
    return 1;
}

int ch;  // 注意用 int,不是 char!因为要容纳 EOF
while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);  // 输出到屏幕
}
fclose(fp);

为什么 fgetc 返回 int 而不是 char

因为 EOF 通常定义为 -1。如果用 unsigned char 接收,当文件里真的有一个字节的值是 255(无符号)或 -1(有符号)时,会和 EOF 混淆。用 int 可以安全区分。

3.2 字符串读写:fgets 与 fputs

c 复制代码
FILE *fp = fopen("note.txt", "r");
char buffer[100];

// 每次读取一行(最多99个字符,预留1个给'\0')
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
    printf("读到: %s", buffer);
}
fclose(fp);

fgets 的三要素:

  • 读到换行符 \n 会停止,并把 \n 也存入缓冲区
  • 读到文件尾或达到 size-1 上限也会停止
  • 自动在末尾补 \0

安全陷阱sizeof(buffer) 千万别写错数组名。如果 arr1arr2 大小不同,却写成 fgets(arr2, sizeof(arr1), fp),会导致缓冲区溢出或读不满。

3.3 格式化读写:fprintf 与 fscanf

这两个函数和 printfscanf 几乎一模一样,只是多了一个文件指针参数

c 复制代码
FILE *fp = fopen("score.txt", "w");
if (fp != NULL) {
    fprintf(fp, "姓名: %s, 分数: %d\n", "张三", 95);
    fclose(fp);
}

读取时:

c 复制代码
FILE *fp = fopen("score.txt", "r");
char name[20];
int score;
fscanf(fp, "姓名: %s, 分数: %d\n", name, &score);
printf("%s 考了 %d 分\n", name, score);
fclose(fp);

注意fscanf 的格式字符串必须和文件内容严格匹配,一个空格对不上就会导致解析失败。


四、二进制读写:fread 与 fwrite

当需要读写结构体、数组或图片等非文本数据时,必须用二进制模式。

c 复制代码
// 写入一个整数数组
int arr[5] = {10, 20, 30, 40, 50};
FILE *fp = fopen("data.bin", "wb");  // 必须带 b

fwrite(arr, sizeof(int), 5, fp);
// 参数:数据地址, 每个元素大小, 元素个数, 文件指针

fclose(fp);

读取回来:

c 复制代码
int arr[5];
FILE *fp = fopen("data.bin", "rb");

size_t count = fread(arr, sizeof(int), 5, fp);
// 返回值是成功读到的元素个数,不是字节数!

if (count == 5) {
    printf("全部读取成功\n");
}

fclose(fp);

文本模式 vs 二进制模式的本质区别:

  • 文本模式下,Windows 会把文件里的 \r\n 自动转成 \n 给你看,写入时反过来
  • 二进制模式下,你读到什么就是什么,一字节不差

五、文件定位:fseek、ftell 与 rewind

文件内部有一个"光标"(文件位置指示器),记录着下一次读写从哪里开始。

5.1 fseek:移动光标

c 复制代码
fseek(fp, offset, origin);
origin 含义 常用场景
SEEK_SET (0) 从文件开头 跳到固定位置
SEEK_CUR (1) 从当前位置 回退或跳过若干字节
SEEK_END (2) 从文件末尾 测文件大小

示例:修改文件中间内容

c 复制代码
FILE *fp = fopen("log.txt", "r+");
fputs("This is an apple.", fp);   // 先写入完整内容

// 把光标拨到第9个字节('n'的位置)
fseek(fp, 9, SEEK_SET);
// 覆盖写入 " sam"
fputs(" sam", fp);

fclose(fp);

执行过程解析:

plaintext 复制代码
原始内容:  T  h  i  s     i  s     a  n     a  p  p  l  e  .
位置:      0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
                                  ↑
                              fseek到这里

覆盖:                            空格 s  a  m
结果:      T  h  i  s     i  s     a 空格 s  a  m  p  l  e  .

最终文件内容变成 This is a sample.,实现了精准替换。

示例:先读后写时的回退技巧

c 复制代码
fputs("abcdefghi", fp);
fflush(fp);

fseek(fp, 1, SEEK_SET);        // 跳到位置1(字符'b')
int ch = fgetc(fp);            // 读取'b',指针自动移到'c'(位置2)

fseek(fp, -1, SEEK_CUR);       // 从当前位置回退1,回到'b'(位置1)
fputs("hello", fp);            // 从'b'开始覆盖

这里第二次 fseek 使用负数偏移 -1,配合 SEEK_CUR,实现了"读了一个字符后退回原位再写入"的效果。如果不回退,fputs 会从 c 的位置开始覆盖,结果就错了。

5.2 ftell:获取当前位置

c 复制代码
fseek(fp, 0, SEEK_END);
long size = ftell(fp);  // 返回值就是文件总字节数

5.3 rewind:一键回开头

c 复制代码
rewind(fp);  // 等价于 fseek(fp, 0, SEEK_SET);

六、错误检测:ferror、feof 与 perror

文件操作出错时,不会直接崩溃,而是设置错误标志。你需要主动检查。

6.1 perror:人类可读的错误信息

c 复制代码
FILE *fp = fopen("不存在的文件.txt", "r");
if (fp == NULL) {
    perror("打开失败");  // 输出:打开失败: No such file or directory
}

6.2 feof 与 ferror:区分"正常结束"和"出错了"

c 复制代码
int ch;
while ((ch = fgetc(fp)) != EOF) {
    putchar(ch);
}

if (feof(fp)) {
    printf("\n正常到达文件末尾\n");
} else if (ferror(fp)) {
    printf("\n读取过程中发生错误\n");
}

重要区分:

  • EOF 可能是"读到结尾",也可能是"读取出错"
  • feof 只在尝试读取失败后才能准确判断是不是因为到结尾了
  • ferror 检测是否发生了读写错误

6.3 clearerr:重置错误标志

一旦出错或到达 EOF,文件指针的错误标志会被设置,后续操作可能受影响。可以用 clearerr(fp) 手动清除这些标志。


七、字符串格式化:sprintf 与 sscanf

虽然这两个函数操作的是字符串 而非文件,但它们与 fprintf/fscanf 属于同一族格式化 I/O 函数,在文件操作中经常配合使用。

如果把 I/O 家族按目标分类:

目标 输出(写) 输入(读)
屏幕/控制台 printf scanf
文件 fprintf fscanf
字符串(字符数组) sprintf sscanf

7.1 sprintf:把数据"打印"到字符串里

c 复制代码
int fileno = 7;
char path[64];
sprintf(path, "data/file_%d.txt", fileno);
// path 内容: "data/file_7.txt"

安全陷阱sprintf 不会检查目标数组够不够大,极易缓冲区溢出。现代 C 开发请改用 snprintf

c 复制代码
snprintf(path, sizeof(path), "data/file_%d.txt", fileno);
// 最多写 sizeof(path)-1 个字符,超长自动截断

7.2 sscanf:从字符串里"扫描"提取数据

c 复制代码
char line[] = "width=1920 height=1080";
int w, h;
sscanf(line, "width=%d height=%d", &w, &h);
// 提取后: w=1920, h=1080

与文件操作配合的典型场景

c 复制代码
FILE *fp = fopen("config.txt", "r");
char buffer[128];

while (fgets(buffer, sizeof(buffer), fp) != NULL) {
    char key[32];
    int value;
    
    // 从读到的每一行里解析 "key = value" 格式
    if (sscanf(buffer, "%s = %d", key, &value) == 2) {
        printf("配置项: %s = %d\n", key, value);
    }
}
fclose(fp);

这里 fgets 负责从文件安全地读一行,sscanf<

相关推荐
2501_937860941 小时前
Java 文件操作与IO(下):字节流、字符流实战IO读写
java·开发语言·python
事圆则缓1 小时前
Kotlin 高阶工程化与 Android 深入实践
android·开发语言·kotlin
驭渊的小故事1 小时前
java抽奖项目-奖品上传中传递参数类型的错误
java·开发语言
名字还没想好☜1 小时前
Go 时间格式化为什么用 2006-01-02:time.Format/Parse 的参考时间、时区与解析踩坑
开发语言·后端·golang·go
Dream Cosmos1 小时前
C++ 多态下篇:虚函数表、动态绑定与多态底层原理
开发语言·c++
2601_9620715710 小时前
【Java报错已解决】org.springframework.beans.factory.BeanCreationException
java·开发语言
淡海水11 小时前
07-04-并发-ConcurrentBag-T-工作窃取WorkStealing算法
开发语言·算法·c#·bag·concurrent·workstealing
CS_Zero11 小时前
C语言sizeof是函数吗?
c语言·开发语言
-今昭-12 小时前
《V2V 迁移实战:将 VMware 虚拟机转为 OpenStack 可用 Glance qcow2 镜像》
开发语言·python