以下是关于 C语言文件操作 的系统性讲解,涵盖最常用的文件操作函数和典型使用场景。我会从基础开始,逐步介绍常用函数、注意事项和实际代码示例。
1. C语言文件操作的基本概念
C语言中文件操作主要通过标准库 <stdio.h> 提供的函数完成。
文件被视为字节流(stream),可以按文本模式或二进制模式打开。
两种主要的文件流类型:
- 文本模式(text mode):
fopen默认方式,处理换行符(Windows:\r\n→\n转换) - 二进制模式(binary mode):加上
b,不做任何转换,常用于图像、音频、可执行文件等
2. 常用文件操作函数一览
| 函数 | 功能 | 返回值成功时 | 常用模式参数示例 |
|---|---|---|---|
fopen | 打开文件 | FILE*(成功)/ NULL | "r", "w", "a", "rb", "wb" 等 |
fclose | 关闭文件 | 0(成功)/ EOF | — |
fread | 从文件读数据(块方式) | 实际读取的元素个数 | — |
fwrite | 向文件写数据(块方式) | 实际写入的元素个数 | — |
fgetc / getc | 读一个字符 | 字符值 / EOF | — |
fputc / putc | 写一个字符 | 写入的字符 / EOF | — |
fgets | 读一行(包含换行符) | 成功返回缓冲区指针 | — |
fputs | 写字符串(不写结束符) | 非负数(成功)/ EOF | — |
fprintf | 格式化输出到文件 | 写入的字符数 / 负值 | — |
fscanf | 格式化输入从文件 | 成功匹配个数 / EOF | — |
feof | 检查是否到达文件末尾 | 非0(到达末尾) | — |
ferror | 检查文件错误状态 | 非0(有错误) | — |
ftell | 获取当前文件位置(字节偏移) | 当前偏移量 / -1L | — |
fseek | 移动文件指针位置 | 0(成功)/ 非0 | SEEK_SET, SEEK_CUR, SEEK_END |
rewind | 将文件指针移到开头 | 无返回值 | — |
remove | 删除文件 | 0(成功)/ 非0 | — |
rename | 重命名文件 | 0(成功)/ 非0 | — |
3. 文件打开模式(fopen 的第二个参数)
| 模式 | 含义 | 文件不存在时 | 文件已存在时 | 指针初始位置 |
|---|---|---|---|---|
"r" | 只读 | 失败 | 打开 | 文件开头 |
"w" | 只写(新建/清空) | 创建 | 清空后写入 | 文件开头 |
"a" | 追加写入 | 创建 | 追加到末尾 | 文件末尾 |
"r+" | 读写 | 失败 | 打开 | 文件开头 |
"w+" | 读写(新建/清空) | 创建 | 清空后可读写 | 文件开头 |
"a+" | 读+追加 | 创建 | 可读,写追加到末尾 | 文件末尾 |
带 b | 二进制模式(如 "rb", "wb") | 同上 | 同上 | 同上 |
注意:
Windows 和 Unix/Linux 在文本模式下对换行符处理不同,建议读写二进制文件时始终使用带 b 的模式。
4. 基本使用流程示例
示例1:逐字符读写(文本文件)
#include <stdio.h>
int main() {
FILE *in = fopen("input.txt", "r");
FILE *out = fopen("output.txt", "w");
if (in == NULL || out == NULL) {
perror("文件打开失败");
return 1;
}
int ch;
while ((ch = fgetc(in)) != EOF) {
fputc(ch, out);
}
fclose(in);
fclose(out);
printf("复制完成\n");
return 0;
}
示例2:按行读取(常用场景)
#include <stdio.h>
#define MAX_LINE 1024
int main() {
FILE *fp = fopen("config.txt", "r");
if (!fp) {
perror("打开失败");
return 1;
}
char line[MAX_LINE];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("读取: %s", line); // fgets 保留换行符
}
fclose(fp);
return 0;
}
示例3:二进制文件读写(结构体)
#include <stdio.h>
typedef struct {
int id;
char name[32];
float score;
} Student;
int main() {
Student s1 = {1, "Alice", 95.5};
Student s2;
// 写入
FILE *fp = fopen("students.dat", "wb");
if (!fp) {
perror("打开失败");
return 1;
}
fwrite(&s1, sizeof(Student), 1, fp);
fclose(fp);
// 读取
fp = fopen("students.dat", "rb");
fread(&s2, sizeof(Student), 1, fp);
printf("ID: %d, Name: %s, Score: %.1f\n", s2.id, s2.name, s2.score);
fclose(fp);
return 0;
}
示例4:随机读写(使用 fseek)
#include <stdio.h>
int main() {
FILE *fp = fopen("data.bin", "r+b");
if (!fp) {
perror("打开失败");
return 1;
}
int num;
// 读取第3个整数(假设每个int占4字节)
fseek(fp, 2 * sizeof(int), SEEK_SET);
fread(&num, sizeof(int), 1, fp);
printf("第3个数: %d\n", num);
// 移动到文件末尾追加
fseek(fp, 0, SEEK_END);
int new_val = 999;
fwrite(&new_val, sizeof(int), 1, fp);
fclose(fp);
return 0;
}
5. 常见问题与注意事项
| 问题 | 解决/注意点 |
|---|---|
| 忘记关闭文件 | 总是用 fclose,尤其是在循环或多文件操作时 |
检查 fopen 返回值 | 永远不要假设打开成功,必须判断是否为 NULL |
使用 feof 判断循环结束 | 错误做法:while(!feof(fp)) 常导致多读一行;正确用 while(fgets(...)) 或检查 fread 返回值 |
| 文本 vs 二进制模式 | 二进制文件一定要用 b 模式,否则 Windows 可能破坏数据 |
| 缓冲区刷新 | fflush(fp) 可强制把缓冲区内容写入文件(对 stdout 也有效) |
| 文件权限问题 | 程序运行目录是否有写权限?(尤其在某些系统目录下) |
| 大文件处理 | fseek 和 ftell 在某些系统对 >2GB 文件可能有问题,可用 fseeko/ftello(POSIX) |
6. 推荐的健壮写法模板(读文件)
FILE *fp = fopen(filename, "r");
if (!fp) {
fprintf(stderr, "无法打开文件: %s\n", filename);
return -1;
}
// 读取操作...
if (ferror(fp)) {
fprintf(stderr, "文件操作发生错误\n");
}
if (fclose(fp) != 0) {
perror("关闭文件失败");
}
总结:最常用的几种模式
| 场景 | 推荐模式 | 常用函数组合 |
|---|---|---|
| 读取配置文件/日志 | "r" | fgets, fscanf |
| 覆盖写入新文件 | "w" | fprintf, fwrite |
| 追加日志 | "a" | fprintf |
| 读写二进制数据(结构体) | "rb", "wb" | fread, fwrite |
| 随机访问文件 | "r+b" | fseek, fread, fwrite |
如果你有具体的使用场景(例如:复制文件、处理 CSV、读写结构体、错误恢复等),可以告诉我,我可以给出更针对性的代码示例或注意事项。