1 引言
文件操作的核心就是三步:打开 → 读写 → 关闭。其中,读写操作可以根据需求选择不同粒度的函数:
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) return 1;
/* 字符写入 */
fputc('A', fp);
/* 字符串写入 */
fputs("Hello\n", fp);
/* 格式化写入 */
fprintf(fp, "数字:%d,浮点数:%.2f\n", 100, 3.14);
fclose(fp);
return 0;
}
本章我们将详细学习这些函数的用法和适用场景。
2 文件的打开:fopen
2.1 函数原型
c
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
-
功能:打开指定文件,返回文件指针
-
参数 :
filename文件名,mode打开模式 -
返回值 :成功返回
FILE*,失败返回NULL
2.2 打开模式详解
2.2.1 文本模式
| 模式 | 含义 | 文件存在 | 文件不存在 |
|---|---|---|---|
"r" |
只读 | 打开成功 | 返回NULL |
"w" |
只写 | 清空内容 | 创建新文件 |
"a" |
追加 | 写入末尾 | 创建新文件 |
"r+" |
读写 | 打开成功 | 返回NULL |
"w+" |
读写 | 清空内容 | 创建新文件 |
"a+" |
读+追加 | 写入末尾 | 创建新文件 |
2.2.2 二进制模式
在文本模式后加 b 表示二进制模式:
c
"rb" /* 二进制只读 */
"wb" /* 二进制只写(清空) */
"ab" /* 二进制追加 */
"rb+" /* 二进制读写 */
"wb+" /* 二进制读写(清空) */
"ab+" /* 二进制读+追加 */
2.2.3 模式选择建议
c
/* 读取配置文件 */
FILE *fp = fopen("config.ini", "r");
/* 写入日志(追加) */
FILE *fp = fopen("log.txt", "a");
/* 创建新文件(覆盖旧文件) */
FILE *fp = fopen("output.txt", "w");
/* 读取二进制文件(如图片) */
FILE *fp = fopen("image.jpg", "rb");
/* 需要同时读写同一个文件 */
FILE *fp = fopen("data.bin", "rb+");
2.3 打开失败的处理
c
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("打开文件失败"); /* 输出具体错误原因 */
/* 或 */
printf("无法打开文件,请检查文件是否存在\n");
return 1;
}
perror 会根据全局变量 errno 输出错误描述,如 No such file or directory。
3 文件的关闭:fclose
3.1 函数原型
c
int fclose(FILE *stream);
-
功能:关闭文件,刷新缓冲区
-
返回值 :成功返回0,失败返回
EOF
3.2 为什么要关闭文件
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) return 1;
fprintf(fp, "重要数据"); /* 数据可能在缓冲区,未写入磁盘 */
fclose(fp); /* 刷新缓冲区,确保数据写入磁盘 */
return 0;
}
关闭文件的三个原因:
-
刷新缓冲区:确保所有数据真正写入磁盘
-
释放资源:释放系统文件句柄(操作系统限制同时打开的文件数)
-
防止数据损坏:程序异常结束可能丢失缓冲区的数据
3.3 忘记关闭的后果
c
void process_files(void)
{
for (int i = 0; i < 10000; i++) {
FILE *fp = fopen("temp.txt", "r");
/* 处理文件... */
/* 忘记 fclose(fp) */
}
} /* 可能耗尽系统文件句柄,后续无法打开文件 */
3.4 关闭后指针置空
c
fclose(fp);
fp = NULL; /* 防止悬空指针 */
4 字符读写函数
4.1 fgetc:读取一个字符
c
int fgetc(FILE *stream);
-
功能:从文件中读取一个字符
-
返回值 :成功返回读取的字符(转为
int);失败或文件结束返回EOF
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
int ch;
while ((ch = fgetc(fp)) != EOF) {
putchar(ch); /* 输出到屏幕 */
}
fclose(fp);
return 0;
}
注意 :返回类型是 int 而不是 char,因为 EOF(通常是-1)无法用 char 表示。
4.2 fputc:写入一个字符
c
int fputc(int c, FILE *stream);
-
功能:将一个字符写入文件
-
返回值 :成功返回写入的字符,失败返回
EOF
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("output.txt", "w");
if (fp == NULL) return 1;
for (char c = 'A'; c <= 'Z'; c++) {
fputc(c, fp);
}
fputc('\n', fp);
fclose(fp);
return 0;
}
4.3 应用:文件复制(字符级)
c
#include <stdio.h>
int copy_file(const char *src, const char *dst)
{
FILE *fsrc = fopen(src, "r");
if (fsrc == NULL) return -1;
FILE *fdst = fopen(dst, "w");
if (fdst == NULL) {
fclose(fsrc);
return -1;
}
int ch;
while ((ch = fgetc(fsrc)) != EOF) {
fputc(ch, fdst);
}
fclose(fsrc);
fclose(fdst);
return 0;
}
int main(void)
{
if (copy_file("source.txt", "target.txt") == 0) {
printf("复制成功\n");
} else {
printf("复制失败\n");
}
return 0;
}
5 字符串读写函数
5.1 fgets:读取一行字符串
c
char *fgets(char *str, int n, FILE *stream);
-
功能 :从文件读取一行(最多
n-1个字符),遇到换行符或文件结束停止 -
特点:会保留换行符(如果读取到的话)
-
返回值 :成功返回
str,失败或文件结束返回NULL
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
printf("读取到:%s", line); /* line 可能包含换行符 */
}
fclose(fp);
return 0;
}
去除换行符:
c
char *p = line;
while (*p) {
if (*p == '\n') {
*p = '\0';
break;
}
p++;
}
5.2 fputs:写入字符串
c
int fputs(const char *str, FILE *stream);
-
功能:将字符串写入文件
-
特点:不会自动添加换行符
-
返回值 :成功返回非负数,失败返回
EOF
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("output.txt", "w");
if (fp == NULL) return 1;
fputs("第一行\n", fp);
fputs("第二行\n", fp);
fputs("第三行", fp); /* 没有换行符 */
fclose(fp);
return 0;
}
5.3 应用:读取配置文件
c
#include <stdio.h>
#include <string.h>
#define MAX_LINE 256
void trim_newline(char *str)
{
size_t len = strlen(str);
if (len > 0 && str[len-1] == '\n') {
str[len-1] = '\0';
}
}
int main(void)
{
FILE *fp = fopen("config.txt", "r");
if (fp == NULL) {
printf("配置文件不存在,使用默认配置\n");
return 0;
}
char line[MAX_LINE];
while (fgets(line, sizeof(line), fp) != NULL) {
trim_newline(line);
/* 跳过空行和注释 */
if (line[0] == '\0' || line[0] == '#') {
continue;
}
printf("配置项:%s\n", line);
}
fclose(fp);
return 0;
}
6 格式化读写函数
6.1 fprintf:格式化写入
c
int fprintf(FILE *stream, const char *format, ...);
c
#include <stdio.h>
int main(void)
{
FILE *fp = fopen("students.txt", "r");
if (fp == NULL) return 1;
char name[50];
int id;
float score;
int count = 0;
while (fscanf(fp, "%s %d %f", name, &id, &score) == 3) {
printf("读取到:%s %d %.1f\n", name, id, score);
count++;
}
printf("共读取 %d 条记录\n", count);
fclose(fp);
return 0;
}
6.3 应用:学生成绩管理
c
#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 100
typedef struct {
char name[50];
int id;
float score;
} Student;
/* 保存数据到文件 */
int save_students(Student students[], int n, const char *filename)
{
FILE *fp = fopen(filename, "w");
if (fp == NULL) return -1;
for (int i = 0; i < n; i++) {
fprintf(fp, "%s %d %.1f\n",
students[i].name,
students[i].id,
students[i].score);
}
fclose(fp);
return 0;
}
/* 从文件读取数据 */
int load_students(Student students[], int *n, const char *filename)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL) return -1;
int count = 0;
while (count < MAX_STUDENTS &&
fscanf(fp, "%s %d %f",
students[count].name,
&students[count].id,
&students[count].score) == 3) {
count++;
}
*n = count;
fclose(fp);
return 0;
}
/* 打印学生列表 */
void print_students(Student students[], int n)
{
printf("\n学生列表:\n");
printf("姓名\t学号\t成绩\n");
printf("----------------\n");
for (int i = 0; i < n; i++) {
printf("%s\t%d\t%.1f\n",
students[i].name,
students[i].id,
students[i].score);
}
}
int main(void)
{
Student students[MAX_STUDENTS];
int n = 0;
/* 加载已有数据 */
if (load_students(students, &n, "students.txt") == 0) {
printf("已加载 %d 条记录\n", n);
print_students(students, n);
}
/* 添加新学生 */
Student new_stu = {"赵六", 1004, 95.0};
students[n++] = new_stu;
/* 保存数据 */
if (save_students(students, n, "students.txt") == 0) {
printf("保存成功\n");
}
return 0;
}
7 三种读写方式的对比
| 函数 | 粒度 | 适用场景 | 特点 |
|---|---|---|---|
fgetc/fputc |
字符 | 逐字符处理、文本分析 | 最灵活,效率最低 |
fgets/fputs |
字符串 | 逐行处理配置文件、文本文件 | 保留换行符,安全(可限制长度) |
fprintf/fscanf |
格式化 | 结构化数据的读写 | 方便,但效率较低,可能有格式问题 |
c
/* 字符级:逐字符复制 */
int ch;
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dst);
}
/* 字符串级:逐行处理 */
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
/* 处理每一行 */
}
/* 格式化读写:结构化数据 */
fprintf(fp, "%s %d %.2f\n", name, id, score);
fscanf(fp, "%s %d %f", name, &id, &score);
8 常见错误与注意事项
8.1 忘记检查文件指针
c
FILE *fp = fopen("data.txt", "r");
fscanf(fp, "%d", &n); /* fp 可能为 NULL! */
8.2 混淆 r+ 和 w+
c
/* r+:不会清空文件,从开头读写 */
FILE *fp = fopen("data.txt", "r+");
/* w+:会清空文件! */
FILE *fp = fopen("data.txt", "w+"); /* 原有数据丢失! */
8.3 fgets 缓冲区大小不足
c
char buf[10];
fgets(buf, 10, fp); /* 最多读取9个字符 + '\0' */
/* 如果一行超过9个字符,只读取前9个,下次读取会继续 */
8.4 fscanf 格式不匹配
c
/* 如果文件是 "abc 100" */
fscanf(fp, "%d", &n); /* 失败!%d 无法匹配 'a' */
/* 返回0,n的值不变 */
8.5 忘记关闭文件
c
FILE *fp = fopen("log.txt", "a");
fprintf(fp, "日志信息\n");
/* 忘记 fclose,数据可能还在缓冲区 */
8.6 对已关闭的文件操作
c
FILE *fp = fopen("data.txt", "r");
fclose(fp);
fgetc(fp); /* 错误!使用已关闭的文件指针 */
8.7 忽略 fscanf 返回值
c
fscanf(fp, "%d", &n); /* 没有检查返回值 */
if (fscanf(fp, "%d", &n) != 1) {
printf("读取失败\n");
}
9 本章小结
本章系统介绍了文件的打开、关闭和三种读写方式:
1. 文件打开:fopen
2. 文件关闭:fclose
3. 字符级读写
4. 字符串级读写
5. 格式化读写
6. 选择建议
-
功能:按格式将数据写入文件
-
用法 :与
printf完全类似,只是多了一个文件指针参数 -
返回值:成功返回写入的字符数,失败返回负数
-
c
#include <stdio.h> typedef struct { char name[50]; int id; float score; } Student; int main(void) { Student students[] = { {"张三", 1001, 88.5}, {"李四", 1002, 92.0}, {"王五", 1003, 78.5} }; int n = 3; FILE *fp = fopen("students.txt", "w"); if (fp == NULL) return 1; for (int i = 0; i < n; i++) { fprintf(fp, "%s %d %.1f\n", students[i].name, students[i].id, students[i].score); } fclose(fp); return 0; }6.2 fscanf:格式化读取
c
int fscanf(FILE *stream, const char *format, ...); -
功能:按格式从文件中读取数据
-
用法 :与
scanf完全类似 -
返回值 :成功匹配并赋值的个数,文件结束返回
EOF -
多种模式:
"r"、"w"、"a"及读写组合 -
二进制模式加
b -
总是检查返回值是否为
NULL -
必须调用,确保缓冲区刷新
-
释放系统资源
-
关闭后指针置空
-
fgetc:读取一个字符,返回int -
fputc:写入一个字符 -
适合逐字符处理
-
fgets:读取一行(安全,有长度限制),保留换行符 -
fputs:写入字符串,不自动添加换行符 -
适合逐行处理文本文件
-
fprintf:格式化写入,类似printf -
fscanf:格式化读取,类似scanf -
适合结构化数据读写
-
注意检查返回值
-
简单复制 →
fgetc/fputc -
文本逐行处理 →
fgets/fputs -
结构化数据 →
fprintf/fscanf