第十章:文件操作
10.1 文件打开模式(r/w/a/r+/w+/a+)
C语言通过 fopen
函数打开文件,需指定文件路径和打开模式。
模式 | 描述 | 适用场景 |
"r" | 只读模式打开文本文件(文件必须存在) | 读取配置文件、日志文件 |
"w" | 只写模式创建或清空文本文件(文件不存在则创建,存在则清空) | 覆盖写入新数据 |
"a" | 追加模式打开文本文件(文件不存在则创建,写入时从文件末尾开始) | 日志追加 |
"r+" | 读写模式打开文本文件(文件必须存在) | 需要同时读写文件内容 |
"w+" | 读写模式创建或清空文本文件(文件不存在则创建,存在则清空) | 覆盖旧文件并支持读写 |
"a+" | 读写模式打开文本文件(文件不存在则创建,写入时从文件末尾开始) | 读取并追加数据 |
二进制模式 | 在模式字符串后加 b (如 "rb" 、"wb+" ),用于处理非文本数据(如图片、音频) | 跨平台二进制文件操作 |
示例:
FILE *fp = fopen("data.txt", "r"); // 只读模式打开文本文件
if (fp == NULL) {
perror("文件打开失败"); // 输出错误信息
exit(1);
}
注意事项:
• 文本模式与二进制模式的区别:
• 文本模式:自动处理换行符(如Windows下\r\n
转换为\n
)。
• 二进制模式:直接读写原始字节。
10.2 文本文件与二进制文件操作
文本文件:
• 以字符形式存储数据(如.txt
文件),适合人类阅读。
• 使用 fprintf
、fscanf
、fgets
、fputs
等函数读写。
示例:写入文本文件
FILE *fp = fopen("data.txt", "w");
fprintf(fp, "Name: Alice\nAge: 25\n"); // 格式化写入
fclose(fp);
二进制文件:
• 以字节流形式存储数据(如结构体、数组),适合保存程序内部数据。
• 使用 fread
、fwrite
函数读写。
示例:写入二进制文件
struct Student {
int id;
char name[20];
float score;
};
struct Student stu = {1001, "Alice", 92.5};
FILE *fp = fopen("data.bin", "wb");
fwrite(&stu, sizeof(struct Student), 1, fp); // 写入整个结构体
fclose(fp);
跨平台注意事项:
• 二进制文件在不同系统上兼容性更好(无换行符转换问题)。
10.3 标准I/O函数(fopen/fread/fwrite/fclose)
核心函数:
函数 | 功能 | 示例 |
fopen(path, mode) | 打开文件 | FILE *fp = fopen("data.txt", "r"); |
fclose(fp) | 关闭文件(释放资源) | fclose(fp); |
fread(ptr, size, n, fp) | 从文件读取数据到内存 | fread(buffer, sizeof(int), 5, fp); |
fwrite(ptr, size, n, fp) | 将内存数据写入文件 | fwrite(arr, sizeof(int), 5, fp); |
fprintf(fp, format, ...) | 格式化写入文件(类似 printf ) | fprintf(fp, "%d %s", 10, "hello"); |
fscanf(fp, format, ...) | 格式化读取文件(类似 scanf ) | fscanf(fp, "%d %s", &num, str); |
示例:读写结构体数组
// 写入二进制文件
struct Student class[3] = {
{1001, "Alice", 90.5},
{1002, "Bob", 85.0},
{1003, "Charlie", 92.0}
};
FILE *fp = fopen("students.bin", "wb");
fwrite(class, sizeof(struct Student), 3, fp);
fclose(fp);
// 读取二进制文件
struct Student new_class[3];
fp = fopen("students.bin", "rb");
fread(new_class, sizeof(struct Student), 3, fp);
fclose(fp);
10.4 文件指针定位(fseek/ftell)
文件指针:
• 每个打开的文件都有一个内部指针,表示当前读写位置。
函数 | 功能 | 示例 |
fseek(fp, offset, origin) | 移动文件指针到指定位置 | fseek(fp, 0, SEEK_END); (移动到末尾) |
ftell(fp) | 返回当前文件指针位置(字节偏移量) | long pos = ftell(fp); |
参数 origin
取值:
• SEEK_SET
:文件开头
• SEEK_CUR
:当前位置
• SEEK_END
:文件末尾
示例:计算文件大小
FILE *fp = fopen("data.bin", "rb");
fseek(fp, 0, SEEK_END); // 移动到文件末尾
long size = ftell(fp); // 获取总字节数
printf("文件大小:%ld 字节\n", size);
fclose(fp);
注意事项:
• 文本模式下 fseek
可能不准确(因换行符转换),建议使用二进制模式。
10.5 错误处理(ferror/feof)
函数 | 功能 | 示例 |
feof(fp) | 检查是否到达文件末尾(返回非零值) | while (!feof(fp)) { ... } |
ferror(fp) | 检查文件操作是否出错(返回非零值) | if (ferror(fp)) { ... } |
perror(msg) | 输出最近一次系统错误信息 | perror("fopen失败"); |
示例:安全读取文件
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("fopen失败");
exit(1);
}
char buffer[100];
while (1) {
size_t read_size = fread(buffer, 1, sizeof(buffer), fp);
if (read_size < sizeof(buffer)) {
if (feof(fp)) {
printf("已到达文件末尾\n");
break;
} else if (ferror(fp)) {
perror("读取失败");
break;
}
}
// 处理读取的数据
}
fclose(fp);
最佳实践:
- 每次I/O操作后检查错误:
if (fwrite(data, sizeof(int), 5, fp) != 5) {
perror("写入失败");
}
- 避免依赖
feof
作为循环条件:应在读取后检查。
总结
- 模式选择:根据需求选择文本或二进制模式,注意跨平台差异。
- 文件指针:合理使用
fseek
和 ftell
实现随机访问。
- 错误处理:始终检查函数返回值并配合
feof
、ferror
确保健壮性。
- 资源释放:文件操作后必须调用
fclose
,避免内存泄漏。
避坑指南:
• 不要忘记关闭文件(使用后立即 fclose
)。
• 二进制文件读写时确保数据类型和大小一致。
• 避免在文本模式下用 fseek
定位到非行首位置。