C语言文件读写完全指南
编程小白也能轻松理解的文件操作知识点汇总
一、文件基础概念
什么是文件?
文件是存储在计算机硬盘、U盘等存储设备上的一组相关数据的集合。就像你电脑里的Word文档、照片、音乐一样。
文本文件 vs 二进制文件
文本文件:内容是人类可读的字符(字母、数字、标点),可以用记事本打开查看。例如:.txt, .c, .html文件。
二进制文件:内容是给计算机看的0和1序列,人类直接看不懂。例如:.exe, .jpg, .mp3文件。
文件指针(FILE指针)
在C语言中,我们通过一个叫FILE指针的东西来操作文件。你可以把它想象成一个遥控器,通过这个遥控器你可以控制文件的各种操作。
每次操作文件前,你需要先获得这个”遥控器”(打开文件),用完后再把它归还(关闭文件)。
FILE *file_ptr;
二、文件的打开与关闭
fopen() – 打开文件
就像开门进房间一样,使用文件前需要先打开它。
函数原型: FILE *fopen(const char *filename, const char *mode);
参数说明:
filename
– 文件路径(如:”test.txt”)mode
– 打开模式(如:”r”表示只读)
文件打开模式
模式 | 说明 | 文件不存在 | 文件存在 |
---|---|---|---|
“r” | 只读(文本文件) | 打开失败 | 打开成功 |
“w” | 只写(文本文件) | 创建新文件 | 清空原有内容 |
“a” | 追加(文本文件) | 创建新文件 | 在末尾添加内容 |
“r+” | 读写(文本文件) | 打开失败 | 打开成功 |
“rb” | 只读(二进制文件) | 打开失败 | 打开成功 |
“wb” | 只写(二进制文件) | 创建新文件 | 清空原有内容 |
fclose() – 关闭文件
就像离开房间要关门一样,用完文件后一定要关闭它!
函数原型: int fclose(FILE *stream);
关闭成功返回0,失败返回EOF(-1)
FILE *fp = fopen(“example.txt”, “w”); // 以写入方式打开
// 检查文件是否成功打开
if(fp == NULL) {
printf(“打开文件失败!\n”);
return 1;
}
// 文件操作代码…
fclose(fp); // 关闭文件
fp = NULL; // 将指针设为NULL防止误用
重要提示:
1. 每次打开文件后一定要检查是否成功(指针是否为NULL)
2. 操作完成后必须关闭文件,否则可能导致数据丢失或文件损坏
3. 关闭文件后最好将指针设为NULL,避免野指针问题
三、文件读写操作
文本文件读写函数
函数 | 作用 | 类比 | 示例 |
---|---|---|---|
fgetc() | 读取单个字符 | 从文件中一个字一个字地读 | char c = fgetc(fp); |
fputc() | 写入单个字符 | 往文件中一个字一个字地写 | fputc(‘A’, fp); |
fgets() | 读取一行字符串 | 从文件中一行一行地读 | fgets(str, 100, fp); |
fputs() | 写入字符串 | 往文件中写一段文字 | fputs(“Hello”, fp); |
fscanf() | 格式化读取 | 按特定格式从文件读取数据 | fscanf(fp, “%d %f”, &num, &price); |
fprintf() | 格式化写入 | 按特定格式将数据写入文件 | fprintf(fp, “ID: %d, Price: %.2f”, id, price); |
文本文件读写示例
FILE *write_fp = fopen(“diary.txt”, “w”);
if(write_fp) {
fprintf(write_fp, “2023年10月25日 晴\n”);
fprintf(write_fp, “今天学习了C语言文件操作\n”);
fputs(“感觉收获满满!”, write_fp);
fclose(write_fp);
}
// 读取文本文件
FILE *read_fp = fopen(“diary.txt”, “r”);
if(read_fp) {
char buffer[100];
while(fgets(buffer, 100, read_fp) != NULL) {
printf(“%s”, buffer);
}
fclose(read_fp);
}
四、二进制文件操作
二进制文件操作适合处理非文本数据,如图片、音频、视频或自定义数据结构。
二进制读写函数
函数 | 作用 | 参数说明 |
---|---|---|
fread() | 从文件读取数据到内存 | fread(存储位置, 每块大小, 块数, 文件指针) |
fwrite() | 将内存数据写入文件 | fwrite(数据地址, 每块大小, 块数, 文件指针) |
二进制文件读写示例
struct Student {
int id;
char name[20];
float score;
};
// 写入二进制文件
FILE *bin_fp = fopen(“students.dat”, “wb”);
if(bin_fp) {
struct Student stu1 = {101, “张三”, 87.5};
struct Student stu2 = {102, “李四”, 92.0};
fwrite(&stu1, sizeof(struct Student), 1, bin_fp);
fwrite(&stu2, sizeof(struct Student), 1, bin_fp);
fclose(bin_fp);
}
// 读取二进制文件
bin_fp = fopen(“students.dat”, “rb”);
if(bin_fp) {
struct Student stu;
while(fread(&stu, sizeof(struct Student), 1, bin_fp) == 1) {
printf(“学号: %d, 姓名: %s, 分数: %.1f\n”, stu.id, stu.name, stu.score);
}
fclose(bin_fp);
}
二进制文件操作小贴士:
✓ 使用sizeof
运算符获取数据类型大小
✓ 检查fread/fwrite的返回值,确保读写正确数量的数据块
✓ 二进制文件不能用文本编辑器查看内容
✓ 二进制文件通常比文本文件更节省空间
五、文件定位操作
文件内部有一个”光标”(位置指针),指示当前读写位置。通过文件定位函数可以移动这个”光标”。
文件定位函数
函数 | 作用 | 示例 |
---|---|---|
fseek() | 移动文件位置指针 | fseek(fp, 10, SEEK_SET); |
ftell() | 获取当前指针位置 | long pos = ftell(fp); |
rewind() | 将指针重置到文件开头 | rewind(fp); |
fseek参数详解
int fseek(FILE *stream, long offset, int whence);
offset: 偏移量(字节数)
whence: 基准位置
SEEK_SET
– 文件开头SEEK_CUR
– 当前位置SEEK_END
– 文件末尾
FILE *fp = fopen(“data.txt”, “r”);
if(fp) {
fseek(fp, 0, SEEK_END); // 移动到文件末尾
long size = ftell(fp); // 获取文件大小
printf(“文件大小: %ld 字节\n”, size);
rewind(fp); // 回到文件开头
// 读取第11个字节开始的10个字符
fseek(fp, 10, SEEK_SET);
char buffer[11];
fread(buffer, 1, 10, fp);
buffer[10] = ‘\0’;
printf(“内容: %s\n”, buffer);
fclose(fp);
}
六、错误处理与状态检测
文件操作可能失败,好的程序应该能处理错误情况。
错误处理函数
函数 | 作用 | 返回值 |
---|---|---|
feof() | 检测是否到达文件末尾 | 非0值表示到达末尾 |
ferror() | 检测文件操作是否出错 | 非0值表示出错 |
perror() | 打印错误信息 | 无 |
FILE *fp = fopen(“nonexistent.txt”, “r”);
if(fp == NULL) {
perror(“打开文件失败”); // 输出: 打开文件失败: No such file or directory
}
// 读取文件时的错误处理
while(!feof(fp)) {
char c = fgetc(fp);
if(ferror(fp)) {
printf(“读取文件时出错!\n”);
clearerr(fp); // 清除错误标志
break;
}
printf(“%c”, c);
}
重要注意事项:
1. 不要使用feof()作为循环条件,应该在循环内检查
2. 文件操作失败后,使用perror()可以获取详细的错误信息
3. ferror()检测到错误后,可以使用clearerr()清除错误标志
4. 文件操作可能因权限不足、磁盘满、文件被占用等原因失败
七、完整示例:学生成绩管理系统
#include <stdio.h>
struct Student {
int id;
char name[20];
float score;
};
void addStudent() {
FILE *fp = fopen(“students.dat”, “ab”); // 追加模式
if(!fp) {
perror(“打开文件失败”);
return;
}
struct Student stu;
printf(“输入学号: “);
scanf(“%d”, &stu.id);
printf(“输入姓名: “);
scanf(“%s”, stu.name);
printf(“输入分数: “);
scanf(“%f”, &stu.score);
fwrite(&stu, sizeof(stu), 1, fp);
fclose(fp);
printf(“添加成功!\n”);
}
void listStudents() {
FILE *fp = fopen(“students.dat”, “rb”);
if(!fp) {
printf(“没有学生记录\n”);
return;
}
struct Student stu;
printf(“学号\t姓名\t分数\n”);
printf(“——————\n”);
while(fread(&stu, sizeof(stu), 1, fp) == 1) {
printf(“%d\t%s\t%.1f\n”, stu.id, stu.name, stu.score);
}
fclose(fp);
}
int main() {
int choice;
do {
printf(“\n学生成绩管理系统\n”);
printf(“1. 添加学生\n”);
printf(“2. 列出学生\n”);
printf(“0. 退出\n”);
printf(“请选择: “);
scanf(“%d”, &choice);
switch(choice) {
case 1: addStudent(); break;
case 2: listStudents(); break;
case 0: printf(“再见!\n”); break;
default: printf(“无效选择\n”);
}
} while(choice != 0);
return 0;
}
实际开发建议:
✓ 文件操作完成后立即关闭文件
✓ 重要数据操作后考虑使用fflush()强制写入
✓ 对于大型文件,考虑分块读写而不是一次性读入内存
✓ 使用二进制文件存储结构化数据更高效
✓ 考虑文件路径的跨平台兼容性(Windows用\,Linux用/)