一、为什么需要文件?
程序运行时的数据都在内存中,程序结束就消失了。文件可以让数据持久化保存。
// 内存中的结构体变量 struct student { char name[20]; int age; double score; long long ID; }; // 文件 = 保存在磁盘上的结构体数据 // 可以保存文字、视频、音乐等本质:文件就是内存中的结构体变量,申请很多字节空间,保存想要保存的信息。
二、FILE结构体
C语言中,文件用FILE结构体表示:
struct _iobuf { char* _ptr; int _cnt; char* _base; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; }; typedef struct _iobuf FILE;
FILE本质上是struct _iobuf的别名,通过typedef定义。
三、文件的路径
3.1 绝对路径 vs 相对路径
| 类型 | 示例 | 说明 |
|---|---|---|
| 绝对路径 | D:\12345\FILE\FILE_type.txt | 从盘符开始的完整路径 |
| 相对路径 | .\FILE\FILE_type.txt | 相对于当前程序的路径 |
生活中的比喻:
绝对路径:中国 陕西省 XX市 XX区 XX大学XX校区 相对路径:XX大学XX校区
3.2 路径的转义字符
// 错误:\ 是转义字符 FILE* p = fopen("D:\12345\FILE\FILE_type.txt", "r"); // 正确:用 \\ 表示 \ FILE* p = fopen("D:\\12345\\FILE\\FILE_type.txt", "r"); // 也可以用 / FILE* p = fopen("D:/12345/FILE/FILE_type.txt", "r");四、fopen:打开文件
FILE* fopen(const char* filename, const char* mode);参数:
filename:文件路径(相对/绝对)mode:操作权限
返回值:
成功:返回文件结构体指针
失败:返回NULL(文件不存在或路径错误)
4.1 常用mode
| mode | 含义 | 文件存在 | 文件不存在 |
|---|---|---|---|
"r" | 只读 | 打开成功 | 返回NULL |
"w" | 只写 | 清空内容 | 创建新文件 |
"a" | 追加 | 在末尾追加 | 创建新文件 |
"r+" | 读写 | 打开成功 | 返回NULL |
"w+" | 读写 | 清空内容 | 创建新文件 |
"a+" | 读写追加 | 末尾追加 | 创建新文件 |
4.2 打开文件示例
#include <stdio.h> int main() { // 相对路径 FILE* p = fopen(".\\FILE\\FILE_type.txt", "r"); // 绝对路径 // FILE* p = fopen("D:\\12345\\FILE\\FILE_type.txt", "r"); if (p == NULL) { printf("文件不存在\n"); return 0; } printf("文件存在,可以操作\n"); fclose(p); return 0; }五、fclose:关闭文件
int fclose(FILE* stream);功能:关闭文件,释放资源。
重要:每次fopen后必须fclose,否则会造成资源泄漏!
FILE* p = fopen("test.txt", "r"); if (p == NULL) return -1; // 使用文件... fclose(p); // 必须关闭六、fgetc/fputc:单字符读写
6.1 fgetc:读取一个字符
int fgetc(FILE* stream);返回值:
成功:返回该字符的ASCII码(int类型)
读到文件末尾:返回EOF(-1)
FILE* p = fopen("test.txt", "r"); if (p == NULL) return -1; char ch; while ((ch = fgetc(p)) != EOF) { printf("%c", ch); } fclose(p);6.2 fputc:写入一个字符
int fputc(int c, FILE* stream);返回值:
成功:返回写入字符的ASCII码
失败:返回EOF
FILE* p = fopen("test.txt", "w"); if (p == NULL) return -1; char arr[] = "春眠不觉晓\n处处闻啼鸟\n"; for (int i = 0; arr[i] != '\0'; i++) { fputc(arr[i], p); } fclose(p);七、fgets/fputs:字符串读写
7.1 fgets:读取一行
char* fgets(char* str, int size, FILE* stream);特性:
遇到
\n或读到size-1个字符停止\n会存入缓冲区,自动追加\0最多读取
size-1个字符(预留1字节存\0)
返回值:
成功:返回缓冲区首地址
读到末尾/失败:返回NULL
FILE* p = fopen("test.txt", "r"); if (p == NULL) return -1; char buffer[128]; while (fgets(buffer, sizeof(buffer), p) != NULL) { printf("%s", buffer); } fclose(p);7.2 fputs:写入字符串
int fputs(const char* str, FILE* stream);特性:
遇到
\0停止不会自动添加换行符
需要换行手动加
\n
FILE* p = fopen("test.txt", "w"); if (p == NULL) return -1; fputs("庆历四年春,\n", p); fputs("滕子京谪守巴陵郡。\n", p); fclose(p);八、fscanf/fprintf:格式化读写
8.1 fprintf:格式化写入
int fprintf(FILE* stream, const char* format, ...);对比:
printf("序号:%d\n", i); // 输出到控制台 fprintf(p, "序号:%d\n", i); // 输出到文件8.2 fscanf:格式化读取
int fscanf(FILE* stream, const char* format, ...);对比:
scanf("%d", &num); // 从控制台读取 fscanf(p, "%d", &num); // 从文件读取8.3 完整示例
// 写入 void FILE_Fprintf() { FILE* p = fopen("fmt.txt", "w"); if (p == NULL) { printf("文件打开失败\n"); return; } for (int i = 1; i <= 3; i++) { fprintf(p, "序号:%d 文本测试\n", i); } fclose(p); } // 读取 void FILE_Fscanf() { FILE* p = fopen("fmt.txt", "r"); if (p == NULL) { printf("文件打开失败\n"); return; } int num; while (fscanf(p, "序号:%d 文本测试\n", &num) != EOF) { printf("读取到序号 = %d\n", num); } fclose(p); } int main() { FILE_Fprintf(); // 先写入 FILE_Fscanf(); // 后读取 return 0; }九、rewind:重置文件指针
void rewind(FILE* stream);功能:将文件指针重新定位到文件开头。
FILE* p = fopen("test.txt", "w+"); if (p == NULL) return -1; // 写入 fputs("庆历四年春,\n滕子京谪守巴陵郡。\n", p); // 指针已经移动到末尾,需要rewind才能从头读取 rewind(p); // 读取 char buffer[128]; while (fgets(buffer, sizeof(buffer), p) != NULL) { printf("%s", buffer); } fclose(p);十、综合实战:从文件读取学生信息
#include <stdio.h> #include <stdlib.h> typedef struct student { char name[20]; int age; double score; char sex[5]; } student; student tulun2510[30]; int len = 0; // 从文件读取 void FILE_ReadWrite_scanf() { FILE* p = fopen("D:\\12345\\FILE\\FILE_type_3.txt", "r"); if (p == NULL) { printf("文件创建失败\n"); return; } printf("文件存在\n"); while (1) { if (feof(p)) break; // 文件结束判断放在最前面 fscanf(p, "%s %d %lf %s\n", tulun2510[len].name, &tulun2510[len].age, &tulun2510[len].score, tulun2510[len].sex); len++; } // 输出 for (int i = 0; i < len; i++) { printf("%s %d %lf %s\n", tulun2510[i].name, tulun2510[i].age, tulun2510[i].score, tulun2510[i].sex); } fclose(p); } // 写入文件 void FILE_ReadWrite_printf() { FILE* p = fopen("D:\\12345\\FILE\\FILE_type_4.txt", "w"); if (p == NULL) { printf("文件创建失败\n"); return; } printf("文件存在\n"); for (int j = 0; j < len; j++) { fprintf(p, "%s %d %lf %s\n", tulun2510[j].name, tulun2510[j].age, tulun2510[j].score, tulun2510[j].sex); } fclose(p); } int main() { FILE_ReadWrite_scanf(); FILE_ReadWrite_printf(); return 0; }注意:
feof(p)的判断位置很重要!放在fscanf后面会污染最后一行导致少输出一行。
十一、面试考点与避坑指南
考点1:fopen的返回值必须检查
FILE* p = fopen("不存在的文件.txt", "r"); if (p == NULL) { // 必须检查 printf("文件打开失败\n"); return -1; }考点2:fclose的重要性
// 忘记fclose会导致: // 1. 文件句柄泄漏 // 2. 缓冲区数据可能未写入磁盘 // 3. 其他程序无法访问该文件 FILE* p = fopen("test.txt", "w"); // ... 操作 ... // 忘记 fclose(p);考点3:EOF的本质
// EOF是常量,值为-1 // fgetc返回int而不是char,就是为了能区分EOF和有效字符 char ch; // 错误:char可能无法表示EOF int ch; // 正确:用int接收 while ((ch = fgetc(p)) != EOF) { putchar(ch); }考点4:fgets和fputs的区别
| 函数 | 操作 | 换行符 |
|---|---|---|
fgets | 读字符串 | 保留\n |
fputs | 写字符串 | 不自动加\n |
考点5:rewind的必要性
FILE* p = fopen("test.txt", "w+"); fputs("hello", p); // 指针移到末尾 // 此时直接读取会读不到任何东西 rewind(p); // 必须重置指针 char buf[100]; fgets(buf, 100, p); // 现在可以读取到"hello"十二、函数速查表
| 函数 | 功能 | 返回值 |
|---|---|---|
fopen | 打开文件 | FILE* / NULL |
fclose | 关闭文件 | 0 / EOF |
fgetc | 读1个字符 | ASCII / EOF |
fputc | 写1个字符 | ASCII / EOF |
fgets | 读一行 | 缓冲区地址 / NULL |
fputs | 写字符串 | 非负值 / EOF |
fscanf | 格式化读 | 成功个数 / EOF |
fprintf | 格式化写 | 字符数 / 负值 |
rewind | 重置指针 | 无 |
feof | 判断文件结束 | 非0 / 0 |
十三、高级总结
| 知识点 | 要点 |
|---|---|
| 文件本质 | 保存在磁盘上的结构体数据 |
| FILE结构体 | typedef struct _iobuf FILE |
| 路径 | 绝对路径 vs 相对路径,\\表示\ |
| 打开模式 | r/w/a/r+/w+/a+ |
| 关闭文件 | 必须fclose,否则资源泄漏 |
| 单字符读写 | fgetc/fputc |
| 字符串读写 | fgets/fputs |
| 格式化读写 | fscanf/fprintf |
| 重置指针 | rewind |
十四、课后作业
实现一个函数,统计文件中某个字符出现的次数
实现一个函数,将文件中的所有小写字母转换为大写
实现一个函数,将两个文件的内容合并到第三个文件
用fprintf/fscanf实现学生信息的存取
以下代码有什么问题?
char ch; while ((ch = fgetc(p)) != EOF) { putchar(ch); }