在 C 语言中,"文件" 的概念比我们日常理解的文本文件(如 .txt, .c)要广泛得多,它包括:

- 磁盘文件:存储在硬盘、U盘等设备上的数据。
- 设备文件:如键盘(标准输入
stdin)、显示器(标准输出stdout)、打印机等。 - 管道文件:用于进程间通信。
- 网络套接字:在更高级的 I/O 中也可以被视为文件。
C 语言通过一个名为 文件指针 的结构体 FILE 来管理所有这些流式输入/输出操作,你可以把 FILE 看作是 C 语言为你打开的一个“文件视图”的窗口,通过这个窗口,你可以读取、写入、定位文件内容。
核心概念:文件指针
FILE 是在 stdio.h 头文件中定义的一个结构体类型,我们通常不关心 FILE 内部的具体细节,而是使用一个指向 FILE 结构体的指针,即 FILE*,来代表一个打开的文件。
#include <stdio.h> FILE *fp; // fp 是一个文件指针,可以指向一个打开的文件
重要: 在进行任何文件操作之前,你必须先通过 fopen() 函数打开一个文件,这个函数会返回一个 FILE* 指针,后续的所有操作都依赖于这个指针。
文件操作的基本流程
一个典型的文件操作遵循“打开-操作-关闭”的流程。

打开文件 fopen()
fopen() 函数用于打开一个文件,并返回一个与之关联的 FILE* 指针。
函数原型:
FILE *fopen(const char *filename, const char *mode);
参数:
filename: 字符串,表示要打开的文件名(可以包含路径)。mode: 字符串,指定文件的打开模式。
常用打开模式:

| 模式 | 含义 | 如果文件不存在 | 如果文件存在 | 读写能力 |
|---|---|---|---|---|
"r" |
只读 | 失败 | 打开成功 | 只能读 |
"w" |
只写 | 创建新文件 | 清空文件内容 | 只能写 |
"a" |
追加 | 创建新文件 | 打开成功,在文件末尾写入 | 只能写 |
"r+" |
读写 | 失败 | 打开成功 | 可读可写,从文件头开始 |
"w+" |
读写 | 创建新文件 | 清空文件内容 | 可读可写 |
"a+" |
读写 | 创建新文件 | 打开成功,在文件末尾写入 | 可读可写,但写入只能在末尾 |
返回值:
- 成功:返回一个指向
FILE结构体的指针。 - 失败:返回
NULL(文件不存在或权限不足)。
示例:
FILE *fp;
fp = fopen("mydata.txt", "w"); // 以只写模式打开 mydata.txt,如果不存在则创建
if (fp == NULL) {
printf("无法打开文件!\n");
// 处理错误,比如退出程序
return 1;
}
文件操作
打开文件后,就可以使用一系列函数对文件进行读写和定位操作。
A. 写入文件
fprintf(): 格式化写入(类似于printf,但输出到文件)。fprintf(fp, "姓名: %s, 年龄: %d\n", "张三", 25);
fputs(): 写入一个字符串(不自动换行)。fputs("这是一行文本,\n", fp);fputc(): 写入一个字符。fputc('A', fp);
B. 读取文件
fscanf(): 格式化读取(类似于scanf,但输入来自文件)。char name[50]; int age; fscanf(fp, "姓名: %s, 年龄: %d\n", name, &age);
fgets(): 从文件中读取一行字符串。char buffer[100]; fgets(buffer, 100, fp); // 最多读取 99 个字符 + 1 个 '\0'
fgetc(): 从文件中读取一个字符。char ch = fgetc(fp);
C. 定位文件指针
rewind(fp): 将文件指针重置到文件的开头。rewind(fp); // 指针回到文件最开始
fseek(fp, offset, whence): 将文件指针移动到指定位置。offset: 偏移量(字节数)。whence: 起始位置。SEEK_SET(0): 文件开头SEEK_CUR(1): 当前指针位置SEEK_END(2): 文件末尾fseek(fp, 10, SEEK_SET); // 从文件开头向后移动 10 个字节 fseek(fp, -5, SEEK_CUR); // 从当前位置向前移动 5 个字节 fseek(fp, 0, SEEK_END); // 移动到文件末尾
ftell(fp): 返回文件指针的当前位置(相对于文件开头的字节数)。long position = ftell(fp); printf("当前指针位置: %ld\n", position);
关闭文件 fclose()
操作完成后,必须调用 fclose() 关闭文件,这会刷新缓冲区(确保所有数据都已写入磁盘),释放系统资源,并使 FILE* 指针失效。
函数原型:
int fclose(FILE *stream);
示例:
if (fclose(fp) == 0) {
printf("文件关闭成功,\n");
} else {
printf("文件关闭失败!\n");
}
完整示例
下面是一个完整的例子,它创建一个文件,写入一些数据,然后重新打开文件读取并打印内容。
#include <stdio.h>
#include <stdlib.h> // 用于 exit()
int main() {
// --- 1. 写入文件 ---
FILE *fp_write = fopen("example.txt", "w");
if (fp_write == NULL) {
perror("打开文件写入失败"); // perror 会打印出更详细的错误信息
return 1;
}
fprintf(fp_write, "Hello, File I/O!\n");
fprintf(fp_write, "This is the second line.\n");
fputs("And this is a third line.\n", fp_write);
fclose(fp_write);
printf("数据已写入 example.txt\n");
// --- 2. 读取文件 ---
FILE *fp_read = fopen("example.txt", "r");
if (fp_read == NULL) {
perror("打开文件读取失败");
return 1;
}
char buffer[256];
printf("\n--- 从文件中读取内容 ---\n");
// 使用 fgets 逐行读取
while (fgets(buffer, sizeof(buffer), fp_read) != NULL) {
// fgets 会读取换行符,printf 不需要再加 \n
printf("%s", buffer);
}
// 检查是否是因为文件结束而停止循环
if (feof(fp_read)) {
printf("\n--- 已到达文件末尾 ---\n");
} else if (ferror(fp_read)) {
perror("读取文件时发生错误");
}
fclose(fp_read);
return 0;
}
错误处理
在文件操作中,错误处理至关重要,常用的检查方法有:
- 检查
fopen返回值:始终检查是否为NULL。 ferror(fp):检查文件操作过程中是否发生错误,在读取失败时调用ferror(fp)可以判断是文件结束还是真正的错误。feof(fp):检查文件指针是否已经到达文件末尾。perror():一个非常有用的函数,它会打印出你提供的字符串,然后附加一个描述最近一次系统错误的字符串("No such file or directory")。
C 文件 I/O 的两种模式
除了上面介绍的基于 FILE* 的缓冲文件 I/O(也称为高层 I/O),C 语言还提供了一组非缓冲文件 I/O(也称为底层 I/O)函数,它们直接使用操作系统提供的文件描述符(int 类型)。
| 功能 | 缓冲 I/O (stdio.h) | 非缓冲 I/O (unistd.h) |
|---|---|---|
| 打开文件 | fopen() |
open() |
| 关闭文件 | fclose() |
close() |
| 读取 | fread(), fgetc(), fgets() |
read() |
| 写入 | fwrite(), fputc(), fputs() |
write() |
| 定位 | fseek(), rewind(), ftell() |
lseek() |
对于初学者和绝大多数应用场景,使用 fopen()、fprintf()、fscanf() 等缓冲 I/O 函数是更简单、更安全的选择。 非缓冲 I/O 提供了更底层的控制,性能可能更高,但使用起来也更复杂,需要自己处理缓冲等细节。
C 语言的“文件视图”就是通过 FILE* 文件指针来实现的,掌握了 fopen、fclose 以及各种读写和定位函数,你就掌握了在 C 语言中进行文件操作的核心技能,打开-操作-关闭”的基本流程,并始终进行错误检查,就能写出健壮的文件操作代码。
