所有的文件操作(无论是文本文件还是二进制文件)都被看作是一个连续的字节流。 程序通过一个指向这个流的指针(FILE* 类型)来读写数据。

(图片来源网络,侵删)
核心概念:文件指针 (FILE*)
在 C 语言中,你不会直接操作文件本身,而是通过一个叫做文件指针 (FILE*) 的对象来间接操作,这个指针指向一个包含了文件相关信息(如文件位置、缓冲区等)的结构体。
你可以把它想象成遥控器,你通过遥控器(文件指针)来控制电视(文件),而不是直接去操作电视内部的电路。
基本文件操作步骤
一个完整的文件操作流程通常分为以下几步:
- 打开文件 (
fopen) - 读写文件 (
fread,fwrite,fgetc,fputc,fgets,fputs,fprintf,fscanf等) - 关闭文件 (
fclose)
重要提示:每次文件操作后,尤其是写操作后,一定要记得关闭文件,这不仅能释放系统资源,还能确保所有缓冲区中的数据都被真正写入到磁盘文件中。

(图片来源网络,侵删)
核心函数详解
打开文件:fopen
fopen 函数用于打开一个文件,并返回一个指向该文件的 FILE 指针。
函数原型:
FILE *fopen(const char *filename, const char *mode);
filename: 字符串,表示要打开的文件名(可以包含路径,如"C:/data/myfile.txt")。mode: 字符串,指定文件的打开模式。
常用打开模式:
| 模式 | 含义 | 如果文件不存在 | 如果文件存在 |
|---|---|---|---|
"r" |
只读 | 失败 | 打开成功,文件指针指向开头 |
"w" |
只写 | 创建新文件 | 清空文件内容,文件指针指向开头 |
"a" |
追加 | 创建新文件 | 打开成功,文件指针指向末尾 |
"r+" |
读写 | 失败 | 打开成功,文件指针指向开头 |
"w+" |
读写 | 创建新文件 | 清空文件内容,文件指针指向开头 |
"a+" |
读写 | 创建新文件 | 打开成功,文件指针指向末尾 |
附加字符:

(图片来源网络,侵删)
"b": 以二进制模式打开。"rb","wb+",在 Windows 系统上区分文本和二进制模式很重要,而在 Linux/macOS 上则影响不大。
返回值:
- 成功:返回一个指向
FILE结构体的指针。 - 失败:返回
NULL指针。
示例:
FILE *fp;
fp = fopen("data.txt", "w"); // 以只写模式打开 data.txt,如果不存在则创建
if (fp == NULL) {
printf("无法打开文件!\n");
// 处理错误
}
关闭文件:fclose
fclose 函数用于关闭一个已经打开的文件,释放相关的资源。
函数原型:
int fclose(FILE *stream);
stream: 要关闭的文件指针。
返回值:
- 成功:返回
0。 - 失败:返回
EOF(通常是一个宏定义为-1)。
示例:
if (fclose(fp) != 0) {
printf("关闭文件时出错!\n");
}
写入文件
a) 写入单个字符:fputc
int fputc(int c, FILE *stream); // 将字符 c 写入到 stream 指向的文件中。 // 成功返回写入的字符,失败返回 EOF。
b) 写入字符串:fputs
int fputs(const char *str, FILE *stream); // 将字符串 str 写入到 stream 中(不写入字符串结束符 '\0')。 // 成功返回非负值,失败返回 EOF。
c) 格式化写入:fprintf
int fprintf(FILE *stream, const char *format, ...); // 按照 format 指定的格式,将数据写入 stream 中。 // 用法与 printf 类似,只是第一个参数是文件指针。
写入示例:
FILE *fp = fopen("output.txt", "w");
if (fp != NULL) {
fputc('A', fp);
fputs("Hello, World!\n", fp);
fprintf(fp, "The value of pi is approximately %.2f\n", 3.14159);
fclose(fp);
}
// output.txt 的内容将是:
// AHello, World!
// The value of pi is approximately 3.14
读取文件
a) 读取单个字符:fgetc
int fgetc(FILE *stream); // 从 stream 中读取一个字符,并返回该字符的 ASCII 值。 // 如果读到文件末尾或出错,返回 EOF。
b) 读取一行:fgets
char *fgets(char *str, int n, FILE *stream); // 从 stream 中读取一行,最多读取 n-1 个字符,并存入 str 中。 // 会在末尾自动添加 '\0',如果读取了换行符,也会包含在 str 中。 // 成功返回 str,失败或到文件末尾返回 NULL。
c) 格式化读取:fscanf
int fscanf(FILE *stream, const char *format, ...); // 按照 format 指定的格式,从 stream 中读取数据。 // 用法与 scanf 类似,只是第一个参数是文件指针。
读取示例:
FILE *fp = fopen("output.txt", "r");
if (fp != NULL) {
char ch;
char line[100];
int num;
// 使用 fgetc 逐个字符读取
printf("逐个字符读取:\n");
while ((ch = fgetc(fp)) != EOF) {
putchar(ch);
}
printf("\n\n");
// 需要重置文件指针到开头才能再次读取
rewind(fp); // 或者 fseek(fp, 0, SEEK_SET);
// 使用 fgets 读取一行
printf("使用 fgets 读取一行:\n");
fgets(line, sizeof(line), fp);
printf("读取到: %s", line);
// 重置文件指针
rewind(fp);
// 使用 fscanf 读取格式化数据
printf("\n使用 fscanf 读取数据:\n");
// 注意:fscanf 会从当前位置开始扫描,可能会跳过空白字符
// 为了找到浮点数,我们可能需要调整逻辑或移动指针
rewind(fp);
while (fscanf(fp, "%f", &num) == 1) {
printf("读取到浮点数: %f\n", num);
}
fclose(fp);
}
二进制文件读写
对于结构体等复杂数据,使用二进制模式更高效。
a) 写入一块数据:fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); // ptr: 指向要写入数据的内存块的指针。 // size: 每个数据项的大小(字节)。 // nmemb: 要写入的数据项的数量。 // stream: 文件指针。 // 返回成功写入的数据项的数量。
b) 读取一块数据:fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); // ptr: 指向存储读取数据的内存块的指针。 // size: 每个数据项的大小(字节)。 // nmemb: 要读取的数据项的数量。 // stream: 文件指针。 // 返回成功读取的数据项的数量。
二进制读写示例:
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[50];
float score;
} Student;
int main() {
Student s1 = {101, "Zhang San", 95.5};
Student s2;
// 写入结构体到二进制文件
FILE *fp_bin = fopen("students.dat", "wb");
if (fp_bin) {
fwrite(&s1, sizeof(Student), 1, fp_bin);
fclose(fp_bin);
printf("结构体 s1 已写入 students.dat\n");
}
// 从二进制文件读取结构体
fp_bin = fopen("students.dat", "rb");
if (fp_bin) {
fread(&s2, sizeof(Student), 1, fp_bin);
fclose(fp_bin);
printf("从文件读取的结构体信息:\n");
printf("ID: %d, Name: %s, Score: %.2f\n", s2.id, s2.name, s2.score);
}
return 0;
}
文件指针定位
rewind(fp): 将文件指针重置到文件开头,等价于fseek(fp, 0, SEEK_SET)。fseek(fp, offset, whence):offset: 偏移量(字节)。whence: 起始位置。SEEK_SET(0): 从文件开头开始计算。SEEK_CUR(1): 从当前位置开始计算。SEEK_END(2): 从文件末尾开始计算。
- 示例:
fseek(fp, 20, SEEK_SET);// 将指针移动到文件开头后的第20个字节。
ftell(fp): 返回当前文件指针的位置(相对于文件开头的字节数)。
错误处理
文件操作可能会因为各种原因失败(如文件不存在、磁盘已满、没有权限等),进行错误检查至关重要。
最佳实践:
- 检查
fopen的返回值:永远不要假设文件打开成功。 - 检查
fread/fwrite的返回值:它们可能返回比你期望的少的数量。 - 使用
feof和ferror:int feof(FILE *stream): 检查文件指针是否到达了文件末尾。int ferror(FILE *stream): 检查文件操作过程中是否发生了错误。
错误处理示例:
FILE *fp = fopen("non_existent_file.txt", "r");
if (fp == NULL) {
perror("打开文件失败"); // perror 会打印 "打开文件失败: " 并附上系统错误信息
// exit(EXIT_FAILURE); // 可以选择退出程序
}
int ch;
while ((ch = fgetc(fp)) != EOF) {
// ... 处理字符 ...
}
if (ferror(fp)) {
printf("读取文件时发生错误!\n");
} else if (feof(fp)) {
printf("成功到达文件末尾,\n");
}
fclose(fp);
| 操作 | 函数 | 说明 |
|---|---|---|
| 打开 | fopen() |
返回 FILE*,失败返回 NULL |
| 关闭 | fclose() |
释放资源,成功返回 0 |
| 写 | fputc() |
写一个字符 |
fputs() |
写一个字符串 | |
fprintf() |
格式化写入 | |
fwrite() |
二进制块写入 | |
| 读 | fgetc() |
读一个字符 |
fgets() |
读一行 | |
fscanf() |
格式化读取 | |
fread() |
二进制块读取 | |
| 定位 | fseek() |
移动文件指针 |
ftell() |
获取当前指针位置 | |
rewind() |
重置指针到开头 | |
| 检查 | feof() |
检查是否到文件末尾 |
ferror() |
检查是否发生错误 | |
perror() |
打印系统错误信息 |
掌握了这些核心函数和概念,你就可以在 C 语言中进行各种复杂的文件操作了。检查返回值是稳健编程的关键。
