C语言文件I/O操作完全指南
文件的本质与分类
在操作系统层面,文件是存储于磁盘等持久化介质上的数据集合,涵盖文本、图像、可执行程序等多种形态。通过文件机制,程序能够实现数据的长期保存与跨会话访问。
依据编码方式差异,文件划分为两大类别:
- 文本文件:数据以ASCII字符形式存储,人类可直接阅读
- 二进制文件:数据按内存原样直接写入,无格式转换
需特别注意:数值型数据可选择两种存储形式,而字符型数据在外存中仅能采用ASCII编码。
流机制与标准通道
C语言通过"流"抽象统一了不同设备的I/O操作。流可理解为字节序列的传输通道,程序借助流与外部世界交互,无需关注底层设备差异。
程序启动时,系统默认开启三个标准流:
| 流名称 | 功能 | 典型应用 |
|---|---|---|
| stdin | 标准输入 | scanf, getchar |
| stdout | 标准输出 | printf, putchar |
| stderr | 标准错误 | perror |
文件操作需显式创建文件流,通过FILE*类型指针关联具体的文件信息区。
文件操作核心流程
所有文件操作遵循固定范式:
- 打开文件 → 建立流连接
- 执行读写 → 数据传输
- 关闭文件 → 释放资源
基础代码框架
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void)
{
FILE* handle = fopen("data.txt", "r");
if (!handle) {
perror("文件打开失败");
return 1;
}
/* 读写操作区域 */
fclose(handle);
handle = NULL;
return 0;
}
文件的打开与关闭
fopen函数
函数原型:FILE* fopen(const char* path, const char* mode);
路径说明:
- 绝对路径:从根目录开始的完整定位,如
/home/user/doc.txt或C:\Users\doc.txt - 相对路径:相对于当前工作目录,如
./config.ini或直接写文件名
常用打开模式:
| 模式 | 含义 | 文件不存在时 |
|---|---|---|
| "r" | 只读文本 | 失败 |
| "w" | 只写文本(覆盖) | 创建新文件 |
| "a" | 追加文本 | 创建新文件 |
| "r+" | 读写文本 | 失败 |
| "w+" | 读写文本(覆盖) | 创建新文件 |
| "rb"/"wb"/"ab" | 二进制对应模式 | 同上 |
fclose函数
函数原型:int fclose(FILE* stream);
成功返回0,失败返回非零值。关闭后应立即将指针置空,避免野指针风险。
顺序读写操作
| 函数 | 功能描述 | 适用范围 |
|---|---|---|
| fgetc | 单字符输入 | 所有输入流 |
| fputc | 单字符输出 | 所有输出流 |
| fgets | 字符串输入 | 所有输入流 |
| fputs | 字符串输出 | 所有输出流 |
| fscanf | 格式化输入 | 所有输入流 |
| fprintf | 格式化输出 | 所有输出流 |
| fread | 二进制块读取 | 仅文件流 |
| fwrite | 二进制块写入 | 仅文件流 |
字符级操作:fgetc与fputc
/* 逐字符读取文件内容 */
int symbol;
while ((symbol = fgetc(handle)) != EOF) {
putchar(symbol);
}
/* 向文件写入字符序列 */
fputc('H', handle);
fputc('i', handle);
字符串操作:fgets与fputs
fgets(buffer, size, stream)最多读取size-1个字符,自动补'\0',遇换行符或EOF停止。
char buffer[128];
while (fgets(buffer, sizeof(buffer), handle)) {
printf("%s", buffer); /* 包含读取的换行符 */
}
fputs不会自动添加换行,需手动控制格式:
fputs("第一行内容\n", handle);
fputs("第二行内容\n", handle);
格式化I/O:fscanf与fprintf
typedef struct {
char id[16];
int quantity;
double price;
} Product;
/* 写入结构化数据 */
Product item = {"A-2024-001", 150, 29.99};
fprintf(handle, "%s %d %.2f\n", item.id, item.quantity, item.price);
/* 读取结构化数据 */
Product temp;
fscanf(handle, "%s %d %lf", temp.id, &temp.quantity, &temp.price);
二进制块操作:fread与fwrite
适用于结构体数组等复杂数据的直接持久化:
/* 写入整数数组 */
int dataset[] = {10, 20, 30, 40, 50};
size_t written = fwrite(dataset, sizeof(int), 5, handle);
/* 读取到结构体数组 */
Product inventory[100];
size_t loaded = fread(inventory, sizeof(Product), 100, handle);
随机位置访问
fseek定位函数
原型:int fseek(FILE* stream, long offset, int origin);
起始位置选项:
SEEK_SET:文件开头(偏移量≥0)SEEK_CUR:当前位置(偏移量可正可负)SEEK_END:文件末尾(偏移量≤0)
/* 跳转到文件第20字节处 */
fseek(handle, 20, SEEK_SET);
/* 回退10字节 */
fseek(handle, -10, SEEK_CUR);
/* 定位到倒数第5字节 */
fseek(handle, -5, SEEK_END);
ftell获取位置
/* 计算文件总大小 */
fseek(handle, 0, SEEK_END);
long filesize = ftell(handle);
rewind(handle); /* 回到开头 */
rewind重置位置
rewind(handle)等价于fseek(handle, 0, SEEK_SET),但更简洁且清除错误标志。
读取结束判定
区分读取结束原因至关重要:
| 函数类型 | 结束标志 | 后续检测 |
|---|---|---|
| fgetc | 返回EOF | feof区分错误/正常结束 |
| fgets | 返回NULL | 同上 |
| fread | 返回值<请求数 | feof/ferror判断原因 |
feof(FILE*)仅在读取操作返回结束后有效,用于确认是否因到达文件尾而终止,而非读取错误。