1 引言
到目前为止,我们所有的程序数据都存储在内存中,程序结束后就消失了。但在实际应用中,我们需要将数据长期保存,比如:
-
保存用户配置,下次启动时读取
-
记录程序运行日志
-
处理外部数据文件
-
生成报告文档
这就需要用到文件操作。C语言通过文件指针和一系列库函数提供了对文件的访问能力。
c
#include <stdio.h>
int main(void)
{
FILE *fp; /* 文件指针 */
/* 打开文件 */
fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("文件打开失败\n");
return 1;
}
/* 写入数据 */
fprintf(fp, "Hello, File!\n");
/* 关闭文件 */
fclose(fp);
return 0;
}
本章我们将深入理解文件操作背后的概念和机制。
2 文件的基本概念
2.1 什么是文件
文件(File)是存储在外部介质(如磁盘)上的数据的集合,是程序进行输入输出的基本单位。操作系统以文件为单位管理数据。
从程序的角度看,文件就是字节序列------一系列可以读取或写入的字节。
2.2 文件的分类
根据数据的组织形式,文件分为两大类:
| 文件类型 | 特点 | 示例 |
|---|---|---|
| 文本文件 | 以字符为单位,每字节存储一个字符的ASCII码 | .txt, .c, .html |
| 二进制文件 | 以字节为单位,按内存中的存储形式原样存放 | .exe, .jpg, .mp3 |
2.3 文本文件
文本文件存储的是字符序列,每个字节对应一个字符的ASCII码。
c
/* 在文本文件中存储整数 12345 */
fprintf(fp, "%d", 12345);
/* 实际写入:'1' '2' '3' '4' '5' 共5字节 */
特点:
-
内容可以直接用文本编辑器查看
-
每行通常以换行符结束(不同系统换行符不同)
-
读写时需要格式转换(如数字与字符串的转换)
-
占用空间通常比二进制大
2.4 二进制文件
二进制文件按照数据在内存中的二进制形式原样存储。
c
/* 在二进制文件中存储整数 12345 */
int n = 12345;
fwrite(&n, sizeof(int), 1, fp);
/* 实际写入:int 的二进制表示,通常4字节 */
特点:
-
不能用文本编辑器直接查看(可能显示乱码)
-
读写速度快,无需格式转换
-
存储紧凑,占用空间小
-
跨平台可能存在字节序问题
2.5 文本文件 vs 二进制文件
| 比较维度 | 文本文件 | 二进制文件 |
|---|---|---|
| 存储形式 | 字符的ASCII码 | 数据的二进制形式 |
| 可读性 | 可直接阅读 | 不可直接阅读 |
| 空间效率 | 较低(如数字12345占5字节) | 较高(int 12345占4字节) |
| 读写效率 | 需格式转换,较慢 | 直接读写,较快 |
| 换行符处理 | 系统自动转换(\n ↔ \r\n) | 不处理,原样写入 |
| 适用场景 | 配置文件、源代码、文档 | 可执行文件、图像、音频 |
3 流的概念
3.1 什么是流
流(Stream)是C语言中对输入输出设备的抽象。它提供了一种统一的方式,无论数据来自键盘、屏幕、文件还是网络,程序都可以用相同的方式读写。
text
程序 ←→ 流 ←→ 设备(文件、键盘、屏幕等)
3.2 流的分类
C语言中的流分为两类:
| 流类型 | 说明 | 示例 |
|---|---|---|
| 文本流 | 以行为单位,处理换行符转换 | 文本文件 |
| 二进制流 | 字节原样传输,不转换 | 二进制文件 |
3.3 标准流
当C程序启动时,系统会自动打开三个标准流:
| 流名称 | 文件指针 | 默认设备 | 用途 |
|---|---|---|---|
| 标准输入 | stdin |
键盘 | 读取输入 |
| 标准输出 | stdout |
屏幕 | 输出正常信息 |
| 标准错误 | stderr |
屏幕 | 输出错误信息 |
c
#include <stdio.h>
int main(void)
{
int n;
fprintf(stdout, "请输入一个整数:"); /* 正常输出 */
fscanf(stdin, "%d", &n); /* 标准输入 */
if (n < 0) {
fprintf(stderr, "错误:输入不能为负数\n"); /* 错误输出 */
return 1;
}
return 0;
}
4 FILE 结构体与文件指针
4.1 FILE 结构体
FILE 是C语言标准库定义的一个结构体类型,用于保存文件的相关信息。它通常包含:
-
文件位置指示器(当前读写位置)
-
缓冲区指针和缓冲区大小
-
文件状态标志(是否到达末尾、是否有错误等)
-
文件描述符(操作系统层面的文件标识)
c
/* 在 stdio.h 中的典型定义(简化示意) */
typedef struct {
int level; /* 缓冲区填充程度 */
unsigned flags; /* 文件状态标志 */
char fd; /* 文件描述符 */
unsigned char hold; /* 无缓冲区时读取的字符 */
int bsize; /* 缓冲区大小 */
unsigned char *buffer; /* 数据缓冲区 */
unsigned char *curp; /* 当前活动指针 */
unsigned istemp; /* 临时文件标志 */
short token; /* 用于有效性检查 */
} FILE;
注意 :在实际编程中,我们不需要 关心 FILE 结构体的内部细节,只需要通过 FILE* 指针使用标准库函数即可。
4.2 文件指针
文件指针 (File Pointer)是指向 FILE 结构体的指针,用于标识一个流,是所有文件操作函数的核心参数。
c
FILE *fp; /* 定义文件指针 */
文件指针的作用:
-
标识打开的文件
-
保存文件的状态信息
-
传递给文件操作函数(如
fprintf、fscanf、fread、fwrite)
4.3 文件指针与普通指针的区别
| 方面 | 文件指针 | 普通指针 |
|---|---|---|
| 指向内容 | FILE 结构体 |
任意类型的数据 |
| 操作方式 | 通过库函数(fread/fwrite等) | 通过解引用(*) |
| 使用者 | 库函数内部维护 | 程序员直接操作 |
5 文件操作的基本流程
5.1 三步曲
文件操作通常遵循三个步骤:
-
打开文件 :
fopen()返回文件指针 -
读写文件 :
fprintf()、fscanf()、fread()、fwrite()等 -
关闭文件 :
fclose()释放资源
c
#include <stdio.h>
int main(void)
{
FILE *fp;
/* 1. 打开文件 */
fp = fopen("example.txt", "r");
if (fp == NULL) {
perror("文件打开失败");
return 1;
}
/* 2. 读写文件(这里只是示例) */
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
/* 3. 关闭文件 */
fclose(fp);
return 0;
}
5.2 为什么需要关闭文件
关闭文件(fclose)非常重要,原因如下:
-
刷新缓冲区:确保所有数据真正写入磁盘
-
释放资源 :释放
FILE结构体和系统文件句柄 -
防止数据丢失:程序异常结束时,缓冲区数据可能丢失
c
/* 错误:忘记关闭文件 */
FILE *fp = fopen("data.txt", "w");
fprintf(fp, "重要数据");
/* 程序结束,但数据可能还在缓冲区,未写入磁盘! */
6 文件打开模式
fopen 的第二个参数指定打开模式,常用的有:
6.1 文本文件模式
| 模式 | 含义 | 文件存在时 | 文件不存在时 |
|---|---|---|---|
"r" |
只读 | 正常打开 | 返回 NULL |
"w" |
只写 | 内容被清空 | 创建新文件 |
"a" |
追加 | 从末尾写入 | 创建新文件 |
"r+" |
读写 | 正常打开 | 返回 NULL |
"w+" |
读写 | 内容被清空 | 创建新文件 |
"a+" |
读和追加 | 从末尾写入 | 创建新文件 |
6.2 二进制文件模式
在文本模式后加 b 表示二进制模式:
| 模式 | 含义 |
|---|---|
"rb" |
二进制只读 |
"wb" |
二进制只写 |
"ab" |
二进制追加 |
"rb+" |
二进制读写 |
"wb+" |
二进制读写(清空) |
"ab+" |
二进制读和追加 |
6.3 模式选择示例
c
FILE *fp;
fp = fopen("data.txt", "r"); /* 读取文本文件 */
fp = fopen("data.txt", "w"); /* 写入文本文件(覆盖) */
fp = fopen("log.txt", "a"); /* 追加到文本文件末尾 */
fp = fopen("image.jpg", "rb"); /* 读取二进制文件 */
fp = fopen("data.bin", "wb"); /* 写入二进制文件 */
7 常见错误与注意事项
7.1 忘记检查文件是否成功打开
c
FILE *fp = fopen("nonexistent.txt", "r");
fprintf(fp, "test"); /* 如果 fp 为 NULL,程序崩溃! */
/* 正确做法 */
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("打开文件失败");
return 1;
}
7.2 忘记关闭文件
c
void process_file(void)
{
FILE *fp = fopen("data.txt", "r");
/* 处理文件... */
/* 忘记 fclose(fp) */
} /* 文件句柄泄漏!程序运行期间无法再次打开该文件 */
7.3 混淆文本模式和二进制模式
在 Windows 系统上,文本模式下换行符 \n 会被自动转换为 \r\n,二进制模式下则不会。
c
/* 在 Windows 上 */
FILE *fp = fopen("test.txt", "w");
fprintf(fp, "Hello\nWorld"); /* 实际写入:Hello\r\nWorld */
fp = fopen("test.bin", "wb");
fprintf(fp, "Hello\nWorld"); /* 实际写入:Hello\nWorld */
7.4 对同一文件进行多次打开
同一个文件可以被多次打开,每次得到不同的文件指针,各自维护独立的位置指示器。
c
FILE *fp1 = fopen("data.txt", "r");
FILE *fp2 = fopen("data.txt", "r");
fgetc(fp1); /* 读取第一个字符,fp1 位置前移 */
fgetc(fp2); /* 也读取第一个字符,fp2 位置独立 */
7.5 使用已关闭的文件指针
c
FILE *fp = fopen("data.txt", "r");
fclose(fp);
fprintf(fp, "test"); /* 错误!fp 已无效 */
7.6 忽略错误处理
文件操作可能因各种原因失败(磁盘满、权限不足等),应该检查返回值:
c
if (fprintf(fp, "data") < 0) {
perror("写入失败");
}
8 本章小结
本章系统介绍了文件的基本概念和文件指针:
1. 文件的类型
-
文本文件:存储字符的ASCII码,可直接阅读
-
二进制文件:存储数据的二进制形式,紧凑高效
2. 流的概念
-
程序与设备之间的抽象,提供统一接口
-
标准流:
stdin、stdout、stderr -
文本流和二进制流的区别
3. FILE 结构体与文件指针
-
FILE结构体保存文件的状态信息 -
文件指针是
FILE*类型,标识一个流 -
程序员只需使用指针,不关心内部细节
4. 文件操作三步曲
-
fopen()打开文件(检查返回值) -
读写操作
-
fclose()关闭文件(必须!)
5. 打开模式
-
"r":只读 -
"w":写入(清空) -
"a":追加 -
"r+"、"w+"、"a+":读写模式 -
加
b表示二进制模式
6. 常见错误
-
忘记检查
fopen返回值 -
忘记关闭文件
-
混淆文本和二进制模式
-
使用已关闭的文件指针
参考资料
1\] C语言标准(ISO/IEC 9899:2018)- 第7.21节:文件访问 \[2\] Kernighan, B. W., \& Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall. \[3\] Prinz, P., \& Crawford, T. (2005). C in a Nutshell. O'Reilly Media. \[4\] 腾讯云开发者社区. C语言文件操作详解 \[5\] CSDN博客. 文本文件与二进制文件的区别 \[6\] 阿里云开发者社区. FILE结构体深度剖析