- 核心概念:文件指针
- 打开和关闭文件:
fopen和fclose - 核心读写函数详解
fwrite和fread(二进制读写)fprintf和fscanf(格式化读写)fputc和fgetc(字符读写)
- 综合实例
- 重要总结与最佳实践
核心概念:文件指针
在 C 语言中,所有文件操作都通过一个指向 FILE 结构体的指针来完成,这个指针被称为文件指针。

FILE 是在 stdio.h 头文件中定义的一个结构体,它包含了文件操作所需的各种信息,比如文件缓冲区、当前读写位置、文件描述符等。
声明文件指针:
FILE *fp; // 声明一个名为 fp 的文件指针
打开和关闭文件:fopen 和 fclose
在对文件进行读写之前,必须先打开它;操作完成后,必须关闭它。
fopen - 打开文件
fopen 函数用于打开一个文件,并返回一个指向该文件的文件指针。

FILE *fopen(const char *filename, const char *mode);
filename: 要打开的文件名(可以包含路径)。mode: 文件打开模式,决定了你如何操作这个文件。
| 模式 | 含义 | 如果文件不存在 | 如果文件存在 |
|---|---|---|---|
"r" |
只读 | 失败 | 打开,文件指针在开头 |
"w" |
只写 | 创建新文件 | 清空文件内容 |
"a" |
追加 | 创建新文件 | 打开,文件指针在末尾 |
"r+" |
读写 | 失败 | 打开,文件指针在开头 |
"w+" |
读写 | 创建新文件 | 清空文件内容 |
"a+" |
读写 | 创建新文件 | 打开,文件指针在末尾 |
注意:
- 在二进制模式下操作文件时,模式字符串后面要加一个
"b","rb","wb","ab+"等,虽然在一些现代操作系统(如 Linux, macOS)上不加"b"也能正常工作,但为了代码的可移植性和明确性,强烈推荐使用。 "w"和"w+"模式会销毁文件原有内容,请务必小心使用。
返回值:
- 成功:返回一个指向
FILE结构体的指针。 - 失败:返回
NULL。
fclose - 关闭文件
fclose 函数用于关闭一个已经打开的文件,并释放其占用的资源。
int fclose(FILE *stream);
stream: 要关闭的文件指针。- 返回值:成功返回
0,失败返回EOF(通常是-1)。
为什么必须关闭文件?

- 刷新缓冲区:写入操作通常是先将数据放入缓冲区,缓冲区满了才真正写入磁盘。
fclose会强制将缓冲区中剩余的数据写入文件。 - 释放资源:操作系统可以同时打开的文件数量是有限的,及时关闭文件可以释放文件句柄等资源。
核心读写函数详解
fwrite 和 fread (二进制读写)
这是最常用、最高效的读写方式,因为它直接在内存和文件之间复制数据块,不进行任何格式转换。
fwrite - 写入数据块
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
ptr: 指向要写入数据的内存地址的指针。size: 每个数据项的大小(单位:字节)。count: 要写入多少个size大小的数据项。stream: 目标文件指针。
返回值:
成功写入的数据项的个数,如果出现错误,返回值可能小于 count。
示例:
#include <stdio.h>
#include <string.h>
struct Student {
int id;
char name[50];
float score;
};
int main() {
FILE *fp = fopen("students.dat", "wb"); // "wb" 表示二进制写入模式
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
struct Student s1 = {101, "Zhang San", 95.5};
struct Student s2 = {102, "Li Si", 88.0};
// 写入第一个学生结构体
fwrite(&s1, sizeof(struct Student), 1, fp);
// 写入第二个学生结构体
fwrite(&s2, sizeof(struct Student), 1, fp);
fclose(fp);
printf("Data written to students.dat\n");
return 0;
}
fread - 读取数据块
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
ptr: 指向存储读取数据的内存地址的指针。size: 每个数据项的大小(单位:字节)。count: 要读取多少个size大小的数据项。stream: 源文件指针。
返回值:
成功读取的数据项的个数,如果到达文件末尾,返回值可能小于 count,如果返回 0,可能表示文件已读完或发生了错误。
示例:
#include <stdio.h>
struct Student {
int id;
char name[50];
float score;
};
int main() {
FILE *fp = fopen("students.dat", "rb"); // "rb" 表示二进制读取模式
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
struct Student s;
// 循环读取,直到文件末尾
while (fread(&s, sizeof(struct Student), 1, fp) == 1) {
printf("ID: %d, Name: %s, Score: %.2f\n", s.id, s.name, s.score);
}
fclose(fp);
return 0;
}
fprintf 和 fscanf (格式化读写)
这两个函数和 printf/scanf 非常相似,只是它们的目标是文件而不是屏幕/键盘。
fprintf - 格式化写入
int fprintf(FILE *stream, const char *format, ...);
stream: 目标文件指针。format: 格式化字符串,和printf一样。- 可变参数,要写入的数据。
示例:
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "w");
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
fprintf(fp, "Hello, World!\n");
fprintf(fp, "The value of pi is approximately: %.2f\n", 3.14159);
fprintf(fp, "User: %s, Age: %d\n", "Alice", 30);
fclose(fp);
printf("Formatted data written to data.txt\n");
return 0;
}
fscanf - 格式化读取
int fscanf(FILE *stream, const char *format, ...);
stream: 源文件指针。format: 格式化字符串。- 可变参数,用于存储读取数据的变量的地址。
返回值:
成功匹配并赋值的字段数量,如果到达文件末尾或发生错误,返回 EOF。
示例:
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("Failed to open file");
return 1;
}
char greeting[50];
float pi;
char name[50];
int age;
fscanf(fp, "%s", greeting); // 读取 "Hello,"
fscanf(fp, "%[^\n]", greeting); // 读取整行 " World!"
printf("Greeting: %s\n", greeting);
fscanf(fp, "The value of pi is approximately: %f", &pi);
printf("Pi: %.2f\n", pi);
fscanf(fp, "User: %s, Age: %d", name, &age);
printf("User: %s, Age: %d\n", name, age);
fclose(fp);
return 0;
}
注意: fscanf 在处理复杂或格式不规范的文本文件时可能不太健壮,对于简单的、格式固定的文件,它很方便。
fputc 和 fgetc (字符读写)
这两个函数用于逐个字符地读写文件,效率较低,但在处理某些特定任务(如配置文件解析)时很有用。
fputc - 写入一个字符
int fputc(int char, FILE *stream);
char: 要写入的字符(类型是int,但通常用char)。stream: 目标文件指针。- 返回值:成功返回写入的字符,失败返回
EOF。
fgetc - 读取一个字符
int fgetc(FILE *stream);
stream: 源文件指针。- 返回值:成功返回读取的字符(类型是
int),如果到达文件末尾或出错,返回EOF。
示例:
#include <stdio.h>
int main() {
// 写入
FILE *fp_out = fopen("char.txt", "w");
if (fp_out) {
fputc('H', fp_out);
fputc('e', fp_out);
fputc('l', fp_out);
fputc('l', fp_out);
fputc('o', fp_out);
fclose(fp_out);
}
// 读取
FILE *fp_in = fopen("char.txt", "r");
if (fp_in) {
int ch;
printf("Reading from char.txt:\n");
while ((ch = fgetc(fp_in)) != EOF) {
putchar(ch); // putchar 是向标准输出(屏幕)写一个字符
}
fclose(fp_in);
}
return 0;
}
综合实例
下面是一个完整的例子,演示如何创建一个简单的学生信息管理系统,将学生信息写入文件,然后再读取出来。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STUDENTS 100
#define FILENAME "students.dat"
typedef struct {
int id;
char name[50];
float score;
} Student;
// 函数声明
void write_students();
void read_students();
int main() {
int choice;
do {
printf("\n--- Student Management System ---\n");
printf("1. Write student data to file\n");
printf("2. Read student data from file\n");
printf("0. Exit\n");
printf("Enter your choice: ");
scanf("%d", &choice);
switch (choice) {
case 1:
write_students();
break;
case 2:
read_students();
break;
case 0:
printf("Exiting...\n");
break;
default:
printf("Invalid choice. Please try again.\n");
}
} while (choice != 0);
return 0;
}
void write_students() {
FILE *fp = fopen(FILENAME, "wb");
if (fp == NULL) {
perror("Error opening file for writing");
return;
}
int num_students;
printf("How many students to add? ");
scanf("%d", &num_students);
Student s;
for (int i = 0; i < num_students; i++) {
printf("\nEnter details for student %d:\n", i + 1);
printf("ID: ");
scanf("%d", &s.id);
printf("Name: ");
scanf("%s", s.name); // 注意:这里不处理带空格的名字
printf("Score: ");
scanf("%f", &s.score);
fwrite(&s, sizeof(Student), 1, fp);
}
fclose(fp);
printf("\nStudent data successfully written to %s\n", FILENAME);
}
void read_students() {
FILE *fp = fopen(FILENAME, "rb");
if (fp == NULL) {
perror("Error opening file for reading");
return;
}
printf("\n--- Reading Student Data ---\n");
Student s;
int count = 0;
while (fread(&s, sizeof(Student), 1, fp) == 1) {
printf("Student %d:\n", ++count);
printf(" ID: %d\n", s.id);
printf(" Name: %s\n", s.name);
printf(" Score: %.2f\n", s.score);
}
if (count == 0) {
printf("No student data found in the file.\n");
}
fclose(fp);
}
重要总结与最佳实践
-
始终检查
fopen的返回值:永远不要假设文件打开成功。fopen返回NULL,程序应该优雅地处理错误(例如打印错误信息并退出),而不是继续操作一个无效的指针。FILE *fp = fopen("my_file.txt", "r"); if (fp == NULL) { // perror 会打印 "my_file.txt: " 然后加上系统错误信息 perror("Error opening my_file.txt"); exit(EXIT_FAILURE); // 使用 exit 终止程序 } -
始终关闭文件:使用
fclose释放资源,确保数据被正确写入磁盘。 -
二进制模式 vs 文本模式:
- 二进制模式 (
"rb","wb"):推荐用于处理结构体、数组等非文本数据,它在不同操作系统之间具有最好的兼容性,因为它不会对换行符等特殊字符进行转换。 - 文本模式 (
"r","w"):用于处理纯文本文件,在 Windows 系统上,文本模式下的\n会被转换为\r\n写入文件,读取时再转换回\n,在 Linux/macOS 上则没有这种转换。
- 二进制模式 (
-
选择合适的读写函数:
fwrite/fread:处理大量数据、结构体、数组等,性能最高。fprintf/fscanf:处理需要人类可读的、格式化的文本文件。fputc/fgetc:逐个字符处理,效率低,适用于特定场景。
-
错误处理:
fread、fwrite等函数的返回值可以用来判断是否成功。ferror函数可以用来检查文件流是否发生了错误。if (fwrite(...) != 1) { if (ferror(fp)) { perror("A write error occurred"); } }
掌握了这些核心函数,你就可以在 C 语言中进行几乎所有的文件操作了。
